Introducción.
Presentación de la serie de datos.
Análisis descriptivo de la base de datos.
Metodología a desarrollar en la práctica.
Análisis preliminar de datos, y primer EDA realizado en Excel.
Tratamiento de datos en RStudio.
Feature engineering.
Selección de variables.
Algoritmos de la librería Caret.
10.1. Random Forest.
10.2. Modelo Multinomial.
10.3. Modelo Xgboost.
10.4. Modelo Red Neuronal.
10.5. Validación cruzada repetida (VCR) de los modelos de Caret.
Algoritmos de la librería H2O.
11.1. Modelo Gradient Boosting(GBM).
11.2. Red Neuronal Profunda.
11.3. Ensamblado (stacking) en H2O.
11.4. Validación cruzada repetida (VCR) de los modelos de H2O.
Comparación Caret vs H2O.
Predicciones sobre el set de test / evaluación final.
Conclusiones.
Referencias.
Anexos.
Como requerimiento necesario para la obtención del grado de Master en Big Data and Business Analytics de la UCM, este trabajo se enmarca en la opción 1 que corresponde al análisis de un set de datos.
En este sentido, me sentí motivado a buscar una base de datos que me permitiera desarrollar un algoritmo de machine learning para un fin específico. La base encontrada correspondió a una colección de información acerca de reservas de hotel, donde mi trabajo se centró en la predicción de la tasa de cancelación de estas reservas. Desde un punto de vista de negocios, la predicción de este hecho particular permitiría a una empresa balancear mejor su capacidad instalada por medio de acciones de revenue management (promoción y pricing) para no ver afectados sus beneficios de manera sorpresiva.
Para cumplir el objetivo, mi trabajo se utilizó herramientas cubiertas en distintos módulos del Máster como minería de datos, machine learning, deep learning entre otros. Respecto de herramientas específicas se usó completamente RStudio y las librerías Caret y H2O para el desarrollo de modelos. Para análisis preliminar de datos se utilizó BI Data Studio de Google. Para el entrenamiento de modelos que requerían mayor potencia de máquina se utilizó la instancia de Compute Engine de Google con Rstudio Server.
La metodología de trabajo correspondió a la estándar aprendida en el módulo de machine learning, que implica el desarrollo de algoritmos individuales, tuneo de hiperparámetros y comparación entre modelos por medio de validación cruzada repetida. De estas comparaciones se seleccionó un mejor algoritmo para el set de datos.
Es importante destacar que no es objetivo de la presente práctica el participar en un concurso de machine learning.
El presente informe está redactado desde un punto de vista descriptivo, gráficas de soporte y código de R se incluye como anexos.
La presente práctica se subió, como HTML, a repositorio de gitHub.
La serie de datos seleccionada corresponde a una base de datos relacionada con diversa información recolectada acerca de reservas de hotel. La base de datos puede descargarse del repositorio de kaggle.com: https://www.kaggle.com/jessemostipak/hotel-booking-demand.
La base completa consta de 119.390 registros y 34 campos.
El detalle de los campos existentes en la base de datos, es el siguiente:
a) hotel (factor 2 niveles): Representa el tipo de hotel en el que se reserva alojamiento. Dominio: City_Hotel y Resort_Hotel.
b) is_canceled (factor 2 niveles): Representa si una reserva fue cancelada o no por el cliente. Dominio: 1 = Reserva fue cancelada y 0 = Reserva no fue cancelada.
c) lead_time (numérica, multinivel): Representa el número de días de anticipación con que el cliente reservó alojamiento en un hotel determinado.
d) arrival_date_year (factor, 3 niveles): Corresponde al año de ingreso del cliente al hotel (check-in). Dominio: 2015, 2016 y 2017.
e) arrival_date_month (factor, 12 niveles): Corresponde al número correspondiente al mes de ingreso del cliente al hotel (check-in). Dominio: 1 a 12.
f) arrival_date_week_number (factor, multinivel): Corresponde al número de semana en que el cliente haría ingreso al hotel. Dominio: 1 a 53.
g) arrival_date_day_of_month (factor, multinivel): Corresponde al día del mes en que el cliente haría ingreso al hotel. Dominio: 1 a 31.
h) stays_in_weekend_nights (numérica, multinivel): Corresponde al número de noches de la semana (lunes a viernes) en que el cliente alojaría en el hotel.
i) stays_in_week_nights (numérica, multinivel): Corresponde al número de noches de fin de semana (lunes a viernes) en que el cliente alojará en el hotel.
j) adults (numérica, multinivel): Corresponde al número de adultos para el que se requiere reserva de alojamiento.
K) children (numérica, multinivel): Corresponde al número de niños para los que se requiere reserva de alojamiento.
l) babies (numérica, multinivel): Corresponde al número de bebés (-2 años) para los que se requiere reserva de alojamiento.
m) meal (factor, 4 niveles): Corresponde al tipo de alimentos que se han contratado junto con la reserva. Dominio: Bead_Breakfast, Half_Board1, Full_Board2 y No_Meal. Este campo presenta valores perdidos.
n) country (factor, multinivel): Corresponde al país del cliente que realiza la reserva en el hotel.
m) Long_av (numérica, multinivel): Corresponde al valor de la longitud promedio del país de procedencia del cliente que se reserva en el hotel.
o) Lat_av (numérica, multinivel): Corresponde al valor de la latitud promedio del país de procedencia del cliente que se reserva en el hotel.
p) market_segment (factor, 7 niveles): Corresponde al segmento de mercado al que pertenece el cliente. Dominio: Aviation, Corporate, Complementary, Direct, Groups, Offline TravelAgens_TurismOperator (TA_TO), Online TA_TO.
q) distribution_channel (factor, 4 niveles): Corresponde al canal de distribución del servicio del hotel. Dominio: Corporate, Direct, GDS (Global Distribution Service) y TA_TO.
r) is_repeated_guest (factor, 2 niveles): Corresponde al identificador si el cliente ha reservado en el pasado o no. Dominio: 1 = Si, el cliente ha reservado en el pasado y 0 = No, el cliente no ha reservado en el pasado.
s) previous_cancellations (numérica, multinivel): Corresponde al identificador del número de reservas canceladas que realizado el cliente en el pasado.
t) previous_bookings_not_canceled (numérica, multinivel): Corresponde al identificador del número de reservas no canceladas que ha realizado el cliente en el pasado.
u) reserved_room_type (factor, 16 niveles): Corresponde a la categoría de habitación reservada por el cliente. Dominio: A a P3.
v) assigned_room_type (factor, 16 niveles): Corresponde a la categoría de habitación finalmente asignada al cliente al momento del check-in. Dominio: A a P4.
w) booking_changes (numérica, multinivel): Corresponde al número de cambios realizados a la reserva de un cliente desde el momento de su ingreso en sistema, a la fecha del check-in en counter.
x) deposit_type: (factor, 3 niveles): Corresponde al tipo de depósito realizado para garantizar la reserva. Dominio: Refundable, Non_Refundable, No_Deposit.
y) agent (numérica, multinivel): Corresponde al código del agente que realiza la reserva en sistema.
z) company (factor, multinivel): Corresponde a la compañía en la que trabaja el cliente que realiza la reserva. La base no tiene nombres de compañías por un tema de privacidad de datos.
aa) days_in_waiting_list (numérica, multinivel): Corresponde al número de días en que un cliente se encuentra en lista de espera por una reserva.
ab) customer_type (factor, 4 niveles): Corresponde al tipo de cliente que realiza la reserva en el hotel. Dominio: Transient5, Transient_party6, Contract7, Group8.
ac) Av_DailyRate (numérica, multinivel): Corresponde al valor por noche, y por persona, en base a la habitación reservada.
ad) required_car_parking_spaces (numérica, multinivel): Corresponde al número de plazas de estacionamiento requeridas en la reserva de habitación.
ae) total_of_special_requests (numérica, multinivel): Corresponde al número de requerimientos especiales realizados por el cliente para la reserva de habitación.
af) reservation_status (factor, 3 niveles): Corresponde a la variable objetivo a predecir. Dominio: Check_Out = Reserva fue ejecutada como estaba planificada, Canceled = Reserva anulada/cancelada antes del check-in y No_Show = Cliente no se presentó en el counter en la fecha prevista. A diferencia de la variable “is_canceled”, consideraré esta variable como objetivo por cuanto la categoría “No_Show” no puede atribuirse a una cancelación de la reserva, como a mi parecer erróneamente, se interpreta la mencionada variable. Se trata pues, no de un problema de clasificación binaria, sino, de un problema de clasificación multiclase.
ag) reservation_status_date (fecha, multinivel): Corresponde a la fecha de la última actualización de la información de la reserva en el sistema del hotel.
Previo desarrollo de los algoritmos creímos necesario obtener una comprensión general de los datos a tratar. En este sentido, una de las preguntas iniciales realizadas fue quién es el cliente que está representado en los datos de la base?.
Los datos nos indican que es un cliente mayoritariamente de origen europeo. Los 5 principales países representados en la base de datos son Portugal, UK, Francia, España y Alemania. Esto ya es un tema, por cuanto vemos que casi no hay representación de personas de América o Asia; sino que además dentro de Europa, el cliente mayoritario es de Portugal9.
La base representa, mayoritariamente, a un viajero de negocios, dado que es una persona que: busca alojamientos de corta estancia (+95% de los datos), habitaciones que cuentan solo con facilidades básicas para dormir, que solo incluyen desayuno (77% de los datos) y en hoteles de ciudad (66% de los datos); en lugar de resorts que explicarían un cliente que busca alojamiento por vacaciones10.
Adicionalmente del viajero representado en los datos, podemos decir que por lo general: viaja solo, su estadía media en un hotel es de app 2,5 noches en días de semana11. En este punto, podríamos decir, aunque los datos no lo indican, su estadía de fin de semana es el domingo, día de check-in para comenzar una semana laboral el lunes temprano.
El gasto medio en habitación, por noche, es cercano a los USD 100.
Analizando los datos desde el punto de vista del negocio hotelero, el cliente que reserva, viene por primera vez en un 97% de los casos. Dado esto, no tiene historial de cancelaciones previas, lo cual es un problema para el el desarrollo de un algoritmo que pretenda pronosticar la probabilidad de cancelación de una reserva en base al historial del cliente.
Por otra parte, el viajero de negocios reserva en su mayoría (+67% de los casos) vía terceros, ya sea agencias de viaje u operadores turísticos. Por las reservas en su gran mayoría, se realizan pagos para garantizarlas (87% de los casos)12.
Respecto del check-in de las reservas, podemos ver que en su mayoría se producen en los meses de Agosto, Julio y Mayo, lo que coincide con el período de vacaciones europeo (particularmente los 2 primeros meses). Esto es extraño, por cuanto se esperaría que los meses más altos para el viajero de negocios no sean los meses de verano. Aun así, existen 5 meses con ingresos de app 10K personas mes: Octubre, Abril, Junio, Septiembre y Marzo.
Con relación al día del mes en el que se produce el check-in con más frecuencia, no hay un patrón claro en esta variable. Si podemos ver una baja relevante a mediados de cada mes, lo que puede ser indicativo de la existencia de un dato atípico que esté afectando la base13.
Al mirar la evolución de las reservas por año, no damos cuenta que 2015 tiene 22K registros, 2016 56K registros y 2017 app. 41K. No sabemos el motivo por el que se presenta este desbalance o cambio de un año a otro, no podemos inferir nada acerca de los motivos del incremento de los números de 2016 vs 2015, ni tampoco de la reducción que se muestra en 2017. Sin embargo, las cancelaciones cada año, fluctúan entre el 35% y el 38% del total de reservas. Las variaciones en esta cifra, tampoco podemos atribuirla a un motivo especial. No tenemos más información en los datos14.
Al abrir la información de reservas por tipo de hotel, notamos que hay una diferencia en la media de reservas realizadas en resorts versus hoteles de ciudad, de app 1 día. El viajero de negocios se hospeda, en promedio 2 noches en hoteles de la ciudad, mientras que el viajero que se hospeda en resorts lo hace, en promedio, 3 noches. El cliente que más tiempo se aloja es el tipo “contract” que lo hace 6 días en resorts. Este cliente sube la media15.
Al mirar la información del status de las reservas (variable objetivo), vemos que un 63% ha llegado a término, un 36% se canceló, y solo un 1% correspondió a reservas confirmadas, en las que el cliente no se presentó en el counter. Esta situación nos muestra una dificultad adicional al momento de construir un algoritmo, en el sentido que estamos lidiando con un desbalance en una variable clave en el entrenamiento de un modelo. Más adelante veremos cómo abordar este punto16.
Al mirar específicamente las cancelaciones, podemos decir que los clientes que más cancelan son los viajeros individuales, esto no sorprende del todo, por cuanto son los mayores viajeros en la base. Sin embargo, las cifras muestran que, en 2015, los viajeros individuales representaron solo el 50% del total de cancelaciones, sin embargo, en 2016 éstos representaron el 87% de las cancelaciones, y en 2017 el 93% de ellas. Esta diferencia respondió a un aumento de los viajes en 2016 y 201717, o es un problema de los datos en 2015?. De acuerdo a los datos de la base, no tenemos la respuesta a esta interrogante18.
Al abrir las cancelaciones por país, no sorprende ver que los primeros lugares los ocupen países que más viajeros muestran en la base: Portugal, UK, España, Alemania y Francia. Si sorprende que el quinto lugar en cancelaciones sea Italia, que en términos de frecuencias de viaje estaba más allá que el lugar 1019.
Respecto de las cancelaciones por tipo de hotel, ellas son app 3,2 veces mayores en los hoteles de ciudad que en los resorts, lo cual es coherente con el tipo de alojamiento que buscan los viajeros de negocios, que son los que más cancelan. En cuanto al segmento de mercado, las mayores cancelaciones se dan para las reservas realizadas por terceros (TA_TO). Llama la atención que el segmento grupos, sea el segundo mayor cancelador de reservas, dado que, en términos de importancia, era el tercero a nivel total. Puede que las anulaciones en las reservas se deban a cancelaciones en las reuniones de negocios de estos grupos por temas presupuestarios en las compañías?20.
Al analizar las tarifas medias que se pagan por tipo de cliente, vemos que los clientes individuales que cancelaron son los que tienen una tarifa más elevada: app USD 110 por noche, lo cual es app USD 6 más elevado que el grupo de viajeros individuales que terminó su reserva en términos planificados (USD 105). Fue el precio el determinante para la cancelación de la reserva?. Si analizamos el grupo de los clientes “Grupo” vemos que los canceladores y los que no se presentan tienen, también tarifas elevadas. El precio, creemos, puede ser un factor en la decisión de cancelación. Veremos con el desarrollo de algoritmos si la variable es significativa21.
Si analizamos las cancelaciones por mes, vemos que ellas siguen un patrón muy parecido a las reservas. Los meses en que más cancelaciones se producen son: Agosto, Julio y Mayo. Por día del mes, el panorama no es distinto, al igual que ocurría con las reservas, no hay un patrón claro que indique algún período del mes donde se producen más cancelaciones. Sin embargo, es posible ver la misma baja a mediados de mes que se observó en las reservas22. Dado que no conocemos cómo se obtuvieron los datos, no tenemos respuesta para este comportamiento.
El objetivo de la práctica es el desarrollar un algoritmo de clasificación que permita predecir la conducta de un cliente frente a una reserva realizada en un hotel genérico23.
La variable a predecir tiene tres categorías, con lo que consideramos el problema como uno de clasificación multiclase. En particular, tres son las opciones que tiene un cliente que reservó una habitación en un hotel: realizar el check-in como estaba planificado, cancelar la reserva antes del día de check-in o no presentarse el día del check-in en el counter. Para el objetivo de predecir estas posibles tres alternativas, proponemos la creación de un algoritmo supervisado que considera el desarrollo de grupos de algoritmos usando la librería de Caret en R. Los algoritmos a cubrir son: Random Forest, Regresión Multinomial, Xgboost y Red neuronal.
Estos algoritmos fueron evaluados individualmente de manera de determinar sus mejores hiperparámetros. Una vez determinados y ajustados éstos, el mejor modelo evaluó su performance en el set de entrenamiento (train, 70% de las observaciones) con la métrica de Accuracy. Posibles sobreajustes fueron contrastados versus el set de validación (20% de las observaciones) con la misma métrica.
Para mantener consistencia, los 4 algoritmos fueron ajustados considerando una grilla de control de validación cruzada con 4 grupos. También se fijó la misma semilla para evitar distintos resultados debidos a cambios en las muestras.
Para la comparación entre algoritmos, se usó validación cruzada repetida y gráfico de cajas para Accuracy en base a mediana y su desviación estándar. Se usaron los mismos grupos, número de iteraciones y semilla24.
Paralelamente, se usó la librería H2O para el desarrollo de algoritmos, que no pudieron ser desarrollados en Caret, ya sea por complejidad o tamaño del set de datos: Gradient Boosting, Red Neuronal Profunda y ensamblado (stacking). Estos algoritmos se compararon versus la mejor alternativa definida por Caret.
Una vez determinado el mejor algoritmo para el set de datos se evaluó, finalmente, el performance de éste versus el set de datos test (10% de las observaciones).
Respecto del set de variables que se usó en los diferentes algoritmos, se utilizó un modelo Random Forest con el fin de reducir las variables a aquellas que más impacto tenían en el accuracy del modelo.
Previa carga de datos a RStudio, se realizaron los siguientes cambios / mejoras a la base de datos: Revisar y eliminar los espacios en blanco en las categorías de las variables.
Realizar cambio en la notación decimal americana (comas para miles, puntos para decimales) por notación Latinoamericana: puntos para los decimales y comas para los miles.
Estandarizar notación para los valores perdidos: Para variables numéricas se recatalogan como “9999”, para variables categóricas, se recatalogan como “Unknown”.
Cambio en las nomenclaturas para facilitar posteriores análisis: Sustitución del código ISO 3155-3:2013 para los países, por su nombre; sustitución del código estándar de “meal hospitality” a su nombre correspondiente, y sustitución del mes de arribo (nombre), por su equivalente numérico.
Dado que la base cuenta con el campo “país” (alfanumérico), se agregaron 2 campos nuevos: Longitud y Latitud medias del respectivo país.
Respaldo de datos en archivo txt separado por tabulaciones, de esta forma evitamos problemas con la notación decimal Latinoamericana.
Una vez cargados los datos a RStudio se procedió al tratamiento de ellos25. Básicamente lo que se realizó a los datos fue lo siguiente:
Revisión y recategorización de variables mal importadas : En este punto, las siguientes variables numéricas pasaron a factor, dado su bajo número de categorías: “is_canceled”, “arrival_date_year”, “arrival_date_month”, “arrival_date_day_of_month”, “is_repeated_guest”, “agent” y “company”.
Las variables “arrival_date” y “reservation_status_date” pasaron a formato fecha.
Agrupamiento de categorías de variables : Se realizó un análisis de las frecuencias de las categorías en las diferentes variables para ver posibles alternativas de agrupamiento de aquellas categorías muy bajo representadas. De esta forma, se creó la categoría “Other” en las siguientes variables: “meal”, “market_segment”, “distribution_chanel”, “reserved_room_type”, “assigned_room_type” y “customer_type”.
Para el caso de la variable “arrival_date_month”, los meses de llegada de los viajeros, se agruparon por trimestres: “Q1”, “Q2”, “Q3” y “Q4”.
En la variable “arrival_date_day_of_month”, los días del mes en el que llegan viajeros se agruparon en mitades. En este sentido, “H1” para los días de la primera mitad del mes y “H2”: para los días de la segunda mitad del mes.
Valores perdidos : Se recatalogaron las variables marcadas como perdidas (NA): “Unknowmn” en variables categóricas, y “9999” en variables numéricas. Este cambio de nombre se realizó en aquellos casos en los que los valores perdidos no eran representativos para una variable en particular. En el caso de los valores “Unknown” en las variables “agent”, “country” y “company”, los valores quedaron con la misma etiqueta para reflejar que corresponde a valores desconocidos, pero que son importantes para el análisis.
Valores fuera de rango : Nos dimos cuenta que la variable “av_daily_rate” incluía valores negativos. Esto no era posible, dado que la variable representa el valor de la habitación por noche, por lo que ellos se renombraron como NA para posterior imputación.
Revisión de las categorías del set de datos: Nuevamente se realizó una revisión de los tipos de variables presentes en el set de datos. Para nosotros solo debería haber variables: factor, numéricas y fechas. Se corrigieron aquellas variables que no tenían alguna de estas 3 clases.
Tratamiento de valores atípicos (outliers) y perdidos (NA) : Se consideraron como valores atípicos, aquellas observaciones que desvíaban de la medía en más de 3 desviaciones estándar; y/o aquellas observaciones que se desviaban de la media, en más de 8 desviaciones absolutas. Aquellos casos se marcaron como NA, para una posterior imputación.
Tanto para el caso de los atípicos, como para los valores perdidos, se verificó que ellos no superaban el 50% de los datos en una variable respectiva. De esta forma era posible su imputación, sin perder información.
El criterio utilizado para imputar valores perdidos (NA) fue el siguiente: media para el caso de los valores perdidos en variables numéricas, y moda, en el caso de las variables categóricas.
Los valores tratados se guardaron en archivo RDS datosLimpios.
Una vez limpio el set de datos, fue el turno de la realización de cambios en las variables previo desarrollo de cualquier algoritmo26.
En este apartado se hicieron los siguientes cambios:
Creación de variables: A partir del set de datos datosLimpios se crearon una serie de variables que buscaban aportar más información: fe_lonLat, que es una variable de distancia a partir de longitud y latitud de cada país. fe_fechaReserva, que determina la fecha en la que se realizó la reserva en el hotel. A partir de ésta y las variables fechas ya existentes en el set de datos, se crearon fe_diaSemanaReserva que muestra numéricamente el día de la semana en que se realizó la reserva (1 = Domingo, 2 = Lunes, etc) y fe_diaSemanaLlegada, que muestra lo mismo, pero para el día de llegada al hotel.
Por último, se creó la fe_totalFactura que estima el valor total de una estadía en el hotel respectivo por las noches contratadas.
Transformación de variables: Se realizó 2 tipos de transformación a variables existentes: uso de frecuencias: para el caso de variables factor con muchas categorías para agrupar, éstas se transformaron a variables numéricas, usando su frecuencia como la misma variable. Este procedimiento se realizó en las siguientes variables: agent, country y company.
aplicación de la función ln(): Esta alternativa se utilizó en el caso de variables numéricas donde existía un sesgo importante respecto de su media (ver anexos 17 y con histogramas de las variables numéricas antes y después de esta transformación). De esta forma, se trató de centrar nuevamente los datos. Las variables modificadas con esta técnica fueron las siguientes: Av_DailyRate, lead_time, stays_in_week_nights, stays_in_weekend_nights, total_of_special_requests y fe_totalFactura.
Eliminación de variables: Las variables transformadas por los 2 métodos descritos anteriormente, se eliminaron del set de datos, por cuanto su “transformada” tomaría su lugar. Adicionalmente se eliminaron variables que no aportaban información al set de datos: is_canceled, prop_missings, fe_fechaArrival, fe_fechaReserva, fe_fechaResStatus y variables con correlación alta entre ellas27: Lat_av y Long_av.
Estandarización y creación de variables dummies: Se procedió a crear variables dummies por cada variable factor. En el caso de las variables numéricas, se estandarizaron las variables que no sufrieron transformaciones vía función ln().
**Formación de los sets de datos para uso de algoritmos: Se crearon 2 sets de datos, compuestos, cada uno, de un set de entrenamiento con un 70% de los valores (83.574 datos), un set de validación con un 20% de las observaciones (23.880 datos), y finalmente un set de test con el 10% restante de los datos. (11.936 observaciones).
Set de datos 1 está compuesto por variables factor convertidas a dummies, más variables numéricas estandarizadas.
Set de datos 2 está compuesto por variables factor convertidas a dummies, más variables numéricas transformadas, más variables numéricas sin transformar estandarizadas. Este set de datos sería de uso eventual en caso de que el set 1 no produjera resultados en accuracy satisfactorios28.
Como fue mencionado en la sección de la metodología de la práctica, usamos un modelo Random Forest sobre el set de datos de entrenamiento (train), para determinar las variables más importantes, y de esta forma reducir la complejidad del set de datos para facilitar el performance de los algoritmos. Tenemos que recordar que Random Forest utiliza un método de selección de mejores variables al momento de crear los quiebres en las ramas de los árboles a construir.
El mismo algoritmo permite obtener la importancia de las variables por medio de la función modelo\(finalModel\)importance(). Esta función entrega la importancia de cada variable respecto de Accuracy y Gini global. Nosotros utilizamos el primer KPI. De esta forma, calculamos las 30 variables que tenían un impacto en la caída del Accuracy de un 90% acumulado. Dicho de otra forma, de eliminar estas 30 variables, el accuracy de nuestro modelo caería un 90%. Por lo tanto, son variables importantes29.
En la práctica, se tunearon 5 algoritmos de Random Forest. Los modelos 1 a 4, sirvieron para la determinación de los mejores hiperparámetros de algoritmo, entrenándose sobre las 60 variables del set de train. El modelo 5, correspondió a la versión final y candidata a compararse con otros algoritmos.
Todos los modelos fueron entrenados con semilla 1234.
Random Forest 1 y 2: Estos modelos se corrieron con el objetivo de determinar un número de árboles óptimo. El modelo 1, consideró una grilla de: 1.000, 1.500, 2.000 y 2.500 arboles. Se fijó mtry como equivalente a sqt(# variables), y equivalente a 8. nodesize se fijó en 10.
El modelo 2 consideró una grilla de:500,2.500,3.000,3.500,4.000,5.000 árboles. En ambos modelos, la grilla de caret incluyó la opción sampling=“down” para manejar el problema del desbalanceo en la variable objetivo mencionado en el punto de análisis de datos31.
El accuracy de los modelos 1 y 2 no varía demasiado con el número de árboles32, fluctuando entre 70,3 y 70,4%. Para la práctica, consideraremos 2.000 árboles como un número apropiado.
Random Forest 3: Este modelo tuvo por finalidad el tuneo del hiperparámetro mtry. Se usó la opción de grilla de datos, incluyendo el vector c(3,4,5,6,7,8,9,10). El número de árboles se determinó del más apropiado de los modelos 1 y 2 e igual a 2.000. No se hicieron cambios en el resto de los parámetros. Caret sugirió que la opción de mtry=8 era la más apropiada.
Los accuracy obtenidos en los set de entrenamiento y validación fueron de un 72,6% y un 71,1% respectivamente33. Estos números no son muy espectaculares, por lo que se realizó un cuarto modelo.
Random Forest 4: Este modelo se tuneó con los hiperparámetros determinados de los modelos anteriores: ntree = 2.000, mtry = 8, nodesize = 10. El único cambio realizado fue la eliminación de la opción sampling=“downn” para ver el efecto final en el accuracy. Los resultados de este modelo se muestran a continuación:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 24.534 | 3.406 | 192 | 28.132 |
| Check_Out | 5.577 | 49.208 | 587 | 55.372 |
| No_Show | 1 | 3 | 66 | 70 |
| Total | 30.112 | 52.617 | 845 | 83.574 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 73.808 | |||
| Accuracy | 88,3% |
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 7.106 | 1.498 | - | 8.604 |
| Check_Out | 967 | 14.067 | - | 15.034 |
| No_Show | 58 | 160 | 24 | 242 |
| Total | 8.131 | 15.725 | 24 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 21.197 | |||
| Accuracy | 88,8% |
Es posible ver que al quitar la opción downsample en caret hizo mejorar el accuracy del modelo de manera importante, dando app 17 puntos más en el KPI en los datos de validación (pasamos de 70,4% a 88,8%). El haber seleccionado downsample redujo de manea relevante el set de datos de validación, desde los 83K registros a los 2,5K registros, siendo una de las razones para resultados tan pobres del modelo 3.
La diferencia entre el accuracy del set de train y validación es app 0,2%, lo que es indicativo que no existe sobreajuste en los datos.
Una vez determinado que incluir downsample al modelo no mejoraba los resultados de accuracy, se usó el modelo 4, para reducir el número de variables 34. Esto dio origen al modelo 5.
Random Forest 5: Este modelo consideró los hiperparámetros ntrees=2.000, nodesize=10, mtry=635, y fue entrenado sobre un set de las 30 variables más importantes.
Los resultados del modelo fueron los siguientes:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 24.634 | 3.693 | 202 | 28.529 |
| Check_Out | 5.477 | 48.924 | 569 | 54.970 |
| No_Show | 1 | 0 | 74 | 75 |
| Total | 30.112 | 52.617 | 845 | 83.574 |
| Diagonal | 73.632 | |||
| Accuracy | 88,1% |
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 7.132 | 1.472 | 0 | 8.604 |
| Check_Out | 1.042 | 13.992 | 0 | 15.034 |
| No_Show | 63 | 153 | 26 | 242 |
| Total | 8.237 | 15.617 | 26 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 21.150 | |||
| Accuracy | 88,6% |
La reducción de variables no afectó el accuracy del modelo, el cual se mantuvo en un 88,1% y 88,6% para los sets de entrenamiento y validación respectivamente. Tampoco hay evidencia de sobreajuste.
Se construyó un segundo modelo: multinomial como una forma de definir un modelo sencillo para el set de datos. De esta forma si el modelo era competitivo, se elegiría éste por sobre modelos más complejos; sobre todo, por el tema de interpretabilidad de los parámetros.
El modelo tuneado se entrenó sobre el set de 30 mejores variables definidas por Random Forest. El mejor modelo se obtuvo con un deacay = 0. La matriz de confusión para el modelo sobre el set de datos train es la siguiente:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 60.812 | 14.902 | 587 | 76.301 |
| Check_Out | 29.524 | 142.949 | 1.948 | 174.421 |
| No_Show | - | - | - | - |
| Total | 90.336 | 157.851 | 2.535 | 250.722 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 203.761 | |||
| Accuracy | 81,3% |
De la tabla anterior, podemos ver que el mejor modelo muestra un accuracy de un 81,3%, que no es un mal resultado, pero es más bajo que el obtenido con Random Forest.
Se generaron las predicciones para el set de validación, y se calculó su matriz de confusión, la que es la siguiente:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 5.794 | 1.428 | 59 | 7.281 |
| Check_Out | 2.808 | 13.606 | 183 | 16.597 |
| No_Show | 2 | 0 | 0 | 2 |
| Total | 8.604 | 15.034 | 242 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 19.400 | |||
| Accuracy | 81,2% |
Vemos que el modelo multinomial es estable, con un accuracy de un 81,2%, muy cercano al obtenido con el set de entrenamiento.
El próximo paso para este modelo es pasar a la prueba de validación cruzada repetida con mismos parámetros definidos para los modelos Caret.
Para este algoritmo, se tunearon 2 modelos: xgboost1: con el objetivo de determinar la mejor combinación de hiperperámetros. En este caso particular, se definió una grilla con las siguientes opciones: min_child_weight=c(5,10,15,20), eta=c(0.1,0.05,0.03,0.01,0.001), nrounds=c(100,500,1000,5000).
De la gráfica de este primer entrenamiento, pudimos concluir que un mayor número de iteraciones (nrounds) dominaba en todos los escenarios de accuracy, con tasas de shirnkage y número de nodos bajos38.
Un segundo modelo, xgboost2, se entrenó con los parámetros ganadores del modelo 1: eta = 0.05 y min_child_weight=5. Se dejó variable el número de árboles, para estudiar el accuracy en el rango entre 1.000 y 5.000 iteraciones39.
El máximo accuracy se produjo con 3.000 iteraciones.
El accuracy de este modelo óptimo40 es de un 88,2%. Es mejor que el logístico, y muy parecido al resultado de Random Forest.
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 25.056 | 4.066 | 200 | 29.322 |
| Check_Out | 5.021 | 48.511 | 479 | 54.011 |
| No_Show | 35 | 40 | 166 | 241 |
| Total | 30.112 | 52.617 | 845 | 83.574 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 73.733 | |||
| Accuracy | 88,2% |
Se calcularon las predicciones para el set de validación, las que arrojaron un accuracy de un 88,9%; lo que es un buen resultado, estable y sin signos de sobreajuste.
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 7.252 | 1.338 | 14 | 8.604 |
| Check_Out | 1.101 | 13.928 | 5 | 15.034 |
| No_Show | 57 | 133 | 52 | 242 |
| Total | 8.410 | 15.399 | 71 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 21.232 | |||
| Accuracy | 88,9% |
Próximo paso para el modelo es evaluar su desempeño en validación cruzada repetida con una semilla distinta.
Se realizaron 2 versiones del algoritmo: red1 con método nnet y red2 con método avNNet. Para ambos, se revisaron los siguientes hiperparámetros: size=c(10,15,20)42 y decay=c(0.1,0.01,0.001)43:
En ambos modelos, el más alto accuracy fue obtenido con 20 nodos en su capa intermedia. De esta forma, la arquitectura de la red fue de 30x20x344.
Respecto del decay óptimo para red1 fue de 0,001 y para red2 fue de 0.1.
El modelo de red neuronal seleccionado para el proceso de validación cruzada repetida (VCR) fue el modelo 1, que presentó un accuracy de un 86,3%, de acuerdo con el siguiente cuadro:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 24.199 | 4.665 | 239 | 29.103 |
| Check_Out | 5.909 | 47.948 | 588 | 54.445 |
| No_Show | 4 | 4 | 18 | 26 |
| Total | 30.112 | 52.617 | 845 | 83.574 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 72.165 | |||
| Accuracy | 86,3% |
Al realizar proyecciones sobre el set de validación, nnet, mostró un accuracy de un 86,6%. Su matriz de confusión es la siguiente:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 6.959 | 1.645 | 0 | 8.604 |
| Check_Out | 1.305 | 13.729 | 0 | 15.034 |
| No_Show | 71 | 169 | 2 | 242 |
| Total | 8.335 | 15.543 | 2 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 20.690 | |||
| Accuracy | 86,6% |
Respecto de la red avNNET, presentó accuracy en entrenamiento y validación de 86,9% y 86,8% respectivamente45. Sin embargo, al probar su desempeño en el proceso de VCR, el requerimiento computacional fue demasiado. El algoritmo estuvo aprendiendo por más de 24 horas en un laptop normal46, y más de 10 horas en Google Compute Engine47 sin generar resultados en ambos casos.
El proceso de validación cruzada repetida se realizó para los 4 modelos tuneados de Caret. Estos modelos se enfrentaron a los mismos parámetros a saber: 10 grupos, 10 repeticiones, semilla=4321 y tamaño de la muestra = 2.00049.
Los 4 modelos se compararon en base a la métrica de Accuracy, considerando su mediana y su desviación estándar.
Los resultados del proceso VCR, pueden verse en la siguiente gráfica50:
Comparación modelos Caret problema de clasificación Reservas Hotel
Podemos concluir que los cuatro algoritmos son buenos modelos, en todos se tiene un accuracy sobre el 85% y con una muy baja desviación estándar en las iteraciones. En esta dimensión, el algoritmo más estable es el multinomial, con una desviación estándar de solo 0.0001, seguido por Random Forest con una desviación similar de 0.0002, pero con 4 puntos más de accuracy (85,8%), lo que lo hace preferido respecto del modelo multinomial.
El algoritmo que más destaca es Xgboost, quien presenta una amplia ventaja respecto del resto de modelos con un accuracy de un 88,4% 51, aun cuando es el algoritmo que posee la más alta desviación, este alto valor de accuracy compensa este último hecho.
Xgboost será el algoritmo elegido en esta parte de la práctica para la librería Caret. El próximo paso es compararlo con el mejor modelo de H2O.
Para completar la práctica quisimos verificar si era posible generar un modelo competitivo al Xgboost de Caret, con la librería H2O en R. Nuestro trabajo se realizó por medio de la interfaz web “Flow” de H2O53, que permitió la generación y entrenamiento de modelos sin necesidad de ingreso de código; y de manera más visual54.
Los modelos que probamos para la base de datos fueron: Gradient Boosting55, Red Neuronal profunda, y a partir de ellos, construimos modelo ensamblado (stacking) con diferentes alternativas de metalearners.
A diferencia que en el apartado de Caret, los modelos una vez generada su mejor versión, pasaron de manera directa a validación cruzada repetida, con los mismos 10 grupos y la misma semilla utilizada en Caret, de manera de mantener consistencia en la comparación.
En Flow probamos varios modelos que nos permitieron determinar buenas combinaciones de hiperparámetros. Particularmente, desarrollamos un modelo con la siguiente configuración56:
Hiperparámetros modelo GBM H2O
En H2O incluimos la opción de early stopping en la configuración del modelo; de manera de evitar el sobreajuste. Se seleccionó la opción de métrica AUTO, para una desviación de 0.001. De esta forma, seleccionamos un alto número de árboles (5.000). Early stopping detuvo las iteraciones a los 375 árboles.
Los resultados obtenidos sobre el set de datos sobre el set de entrenamiento fueron los siguientes:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 29.892 | 214 | 6 | 30.112 |
| Check_Out | 130 | 52.486 | 1 | 52.617 |
| No_Show | 8 | 37 | 800 | 845 |
| Total | 30.030 | 52.737 | 807 | 83.574 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 83.178 | |||
| Accuracy | 99,5% |
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 7.296 | 1.303 | 5 | 8.604 |
| Check_Out | 1.043 | 13.986 | 5 | 15.034 |
| No_Show | 55 | 134 | 53 | 242 |
| Total | 8.394 | 15.423 | 63 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 21.335 | |||
| Accuracy | 89,3% |
Podemos ver que el modelo entrega un accuracy de un 99,5% y un 89,3% para los sets de entrenamiento y validación respectivamente. Estos números podrían sugerir sobreajuste, sin embargo, al revisar la gráficas de las curvas de error, no se evidencia este hecho, gracias a early stopping57:
Dados los resultados obtenidos por nnet en Caret, siendo el segundo mejor modelo, nos preguntamos si era posible, aumentando la complejidad de una red, obtener mejores accuracy. De esta forma construimos 4 modelos usando la opción Deep Learning de la librería H2O.
Se desarrollaron 4 modelos con distintas estructuras de red58: deep1: con estructura [30,200,200,3], deep2: con estructura [30,512,3], deep3: con estructura [30,64,64,64,3] y por último deep4: con estructura [30,32,32,32,32,32,3].
En el proceso de desarrollo, no se incluyó rectificación, ni dropout; opciones que se utilizarían en caso de obtener valores de accuracy bajos.
Los 4 modelos fueron construidos con funciones de pérdida cross entropy, función de activación tanh, learning rate de 0.0001 y función multinomial. Se incluyó early stopping con función longloss, criterio 0.0001 y un número de iteraciones (epochs) de 20.
Para ahorrar tiempo de proceso, se usó la opción de scoring para entrenamiento y validación: de esta forma, estos procesos se realizaron con muestras de train de 10.000 datos (80%) y de validación de 2.500 datos (20%); en lugar que con la totalidad del set de datos.
Todos los modelos incluyeron la opción de validación cruzada repetida con 10 grupos y misma semilla que la usada en Caret.
Los mejores valores de accuracy se obtuvieron con las configuraciones de deep2 y deep459, que fueron las siguientes:
Los resultados obtenidos por la red 2 en train y validación fueron: 79,9% y 79,6% respectivamente. Estos resultados no son especialmente espectaculares; sin embargo, nuestro objetivo es usar la red en un modelo stacked, no de forma individual.
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 1.790 | 1.694 | 0 | 3.484 |
| Check_Out | 203 | 6.146 | 0 | 6.349 |
| No_Show | 11 | 88 | 0 | 99 |
| Total | 2.004 | 7.928 | 0 | 9.932 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 7.936 | |||
| Accuracy | 79,9% |
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 489 | 452 | 0 | 941 |
| Check_Out | 47 | 1.531 | 0 | 1.578 |
| No_Show | 2 | 18 | 0 | 20 |
| Total | 538 | 2.001 | 0 | 2.539 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 2.020 | |||
| Accuracy | 79,6% |
Una de las características de esta red con alto número de neuronas en solo 1 capa es su alta variabilidad en los datos, lo que puede ser apreciado su gráfica de errores60.
Para el caso de la red4, los resultados de accuracy fueron:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 2.484 | 1.000 | 0 | 3.484 |
| Check_Out | 504 | 5.845 | 0 | 6.349 |
| No_Show | 23 | 76 | 0 | 99 |
| Total | 3.011 | 6.921 | 0 | 9.932 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 8.329 | |||
| Accuracy | 83,9% |
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 663 | 278 | 0 | 941 |
| Check_Out | 133 | 1.445 | 0 | 1.578 |
| No_Show | 5 | 15 | 0 | 20 |
| Total | 801 | 1.738 | 0 | 2.539 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 2.108 | |||
| Accuracy | 83,0% |
Para la red deep4, los valores de accuracy para entrenamiento y validación fueron de 83,9% y 83%. Son valores mejores que los obtenidos con la red 2, pero inferiores a los que se podía observar con Caret. Podemos ver que para los datos, es mejor una red con más capas ocultas y menos neuronas, que la opción inversa: más neuronas y menos capas ocultas61.
Con Deep Learning, no fue posible armar un modelo que generara más de un 85% de accuracy. No obstante, los resultados obtenidos, los accuracy generados en el proceso VCR fueron: 82,8% y 84,4% para las configuraciones de redes 2 y 4 respectivamente.
Como otra de las ventajas del uso de H2O, estuvo el hecho de poder “ensamblar” modelos usando algoritmos preentrenados como variables independientes para un “metalearner”. Para nuestro set de datos, utilizamos los algoritmos Gradient Boosting, Deep learning 2 y Deep learning 4 con los siguientes metalearners: GLM, Random Forest y Gradient Boosting. En todos los casos se usó validación cruzada repetida con 10 grupos y misma semilla que la usada en Caret.
La idea detrás de ensamblar un modelo fue aprovechar la alta varianza de los modelos de red para obtener valores de accuracy más elevados, pero con la estabilidad del modelo Gradient Boosting. Las gráficas de las funciones de error, muestran que los picos y valles que se podían ver en los modelos de red, se han suavizado62.
Como se realizó con Caret, se tomaron los valores de accuracy de cada modelo para cada grupo de VCR, los datos se ordenaron y graficaron usando boxplot() para apreciar qué modelo lo hace mejor.
La gráfica que compara los modelos de H2O es la siguiente63:
Hiperparámetros modelo GBM H2O
De la gráfica, podemos ver claramente que los modelos de red presentan una más alta varianza y una mediana en accuracy más baja. El modelo Gradient Boosting, como se esperaba, muestra un muy buen accuracy y estable. Sin embargo, los 3 modelos ensamblados se desempeñan mejor que este último, siendo el stacked con metalearner GLM el que genera el accuracy más alto y cercano al 90%. Este modelo se selecciona como finalista de la librería H2O.
El modelo stacked GLM, comparado con Xgboost de Caret puede verse en el siguiente gráfico de cajas sobre sus VCR.
Comparación Xgboost Caret vs mejor Stacked GLM H2O
Al realizar una comparación con el modelo Xgoost entrenado con Caret, podemos ver que el modelo ensamblado se desempeña mejor que éste, con una diferencia de casi un punto en sus medianas64. A pesar de presentar una varianza mas elevada, el valor mínimo observado es superior al valor máximo que es posible alcanzar con Xgboost. Este hándicap es que buscamos y el que nos hace decidir por este modelo como el mejor para nuestra serie de datos.
Como último paso en nuestra práctica, para los 2 modelos finalistas, se realizó carga del set de datos de test, para evaluar el performance final del modelo.
Para el caso del modelo Xgboost de Caret, las predicciones sobre test entregaron un accuracy de un 88,6%. La siguiente es su matriz de confusión:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 3.566 | 724 | 11 | 4.301 |
| Check_Out | 528 | 6.982 | 5 | 7.515 |
| No_Show | 26 | 66 | 28 | 120 |
| Total | 4.120 | 7.772 | 44 | 11.936 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 10.576 | |||
| Accuracy | 88,6% |
Para el caso del modelo stacking 2 con metalearner GLM de H2O, las predicciones sobre el set de test entregan un accuracy de un 89,1%. La siguiente es su matriz de confusión:
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 3.581 | 714 | 6 | 4.301 |
| Check_Out | 478 | 7.029 | 8 | 7.515 |
| No_Show | 24 | 71 | 25 | 120 |
| Total | 4.083 | 7.814 | 39 | 11.936 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 10.635 | |||
| Accuracy | 89,1% |
Aún, cuando la diferencia no es importante, Como se esperaba, el modelo stacked GLM mostró un desempeño que es superior al modelo Xgboost. De esta forma, es el algoritmo declarado como el mejor modelo para nuestro set de datos.
Como conclusiones a la práctica puedo mencionar:
La base de datos utilizada correspondió a una base disponible en un repositorio público (Kaggle.com). Este hecho tuvo como principal implicancia el bajo control en el proceso de obtención de los datos. Pudimos observar ciertos sesgos en la información de la base, asociada por ejemplo al tipo de cliente, que no pudimos explicar dada la nula información adicional con que contamos. Adicionalmente nuestra investigación no se pudo orientar a un objetivo concreto, más que exploración y análisis general, por el mismo motivo; es decir, no conocer la fuente de obtención de datos y los objetivos concretos de su colección.
El mejor algoritmo para predecir la tasa de cancelación en reservas de hotel para nuestra base de datos, es un modelo ensamblado formado por un modelo Gradient Boosting y 2 redes neuronales con configuración [30x512x3] y [30x32x32x32x32x32x3]. Este modelo usa un metalearner de GLM, y explica correctamente un 89,1% de los datos del set de test, mostrando estabilidad en su desempeño.
No obstante el punto anterior, creemos que el modelo puede volver a la etapa de diseño para probar algunos cambios en parámetros, no probados en esta instancia, de manera de ver posibles mejoras marginales en el accuracy observado. Particularmente parámetros que no se tocaron en las redes fueron las tasas de dropout en capas input y ocultas, y los parámetros de rectificación.
Un segundo buen modelo para ser usado en nuestra base es un modelo Xgboost de la librería Caret. Este modelo es capaz de predecir correctamente un 88,6% de los datos del set de test.
Pudimos comprobar las bondades de los modelos ensamblados (stacking). Todos mos modelos ensamblados que se usaron en la base (mezcla de Gradient Boosting y Redes Neuronales con distintos metalearners), presentaron un mejor desempeño, en accuracy, que sus modelos individuales en la librería H2O.
Validamos Random Forest como un muy buen modelo para ser usado en etapas iniciales de construcción de algoritmos, en la medida que permite reducir variables manteniendo el poder de predictibilidad con las variables seleccionadas. En nuestro caso, pudimos eliminar la mitad de las variables del set de datos, pasando de 60 a 30 variables, simplificando los posteriores modelos.
Dado el desbalanceo presente en la variable objetivo, probamos con usar remuestreo con “downsample” (categorías más representadas bajan su número respecto de las menos representadas en el proceso de muestreo) para tratar de corregir el problema y no afectar las predicciones de nuestros modelos. Este proceso se llevó a cabo en Random Forest, con resultados pobres en accuracy; es decir, no se mejoraron las predicciones en el modelo construido (accuracy bajo 80%) versus los modelos sin esta opción de corrección. Para futuros estudios recomendamos probar con paquete Rose en Caret, para evaluar su desempeño.
Base de datos Hotel Booking Demand, Jesse Mostipak, disponible en kaggle (https://www.kaggle.com/jessemostipak/hotel-booking-demand). Esta base fue obtenida primariamente desde Hotel Booking Demand Datasets de Nuno Antonio, Ana Almeida, and Luis Nunes for Data in Brief, Volume 22, February 2019 (https://www.sciencedirect.com/science/article/pii/S2352340918315191).
Deep Learning, Quick Referene; Mike Bermico. 2018.
Machine Learning Using R, Karthik Ramasubramanian y Abhishek Singh. 2nd Edition, 2019.
Códigos utilizados fueron extraídos en su mayoría de apuntes de los módulos minería de datos y machine learning.
País de origen de quien busca habitación en un hotel
No tenemos más información del viajero representado en los datos. Sin embargo, es posible apreciar que en su mayoría es europeo. Llama la atención la muy baja representación de personas de Asia y América. Los principales países de origen son Portugal, UK, Francia, España y Alemania.
Dónde y cómo aloja el cliente tipo de la base de datos.
Cliente mayoritariamente de negocios, que busca estadías cortas en hoteles con comodidades básicas.
Tipo de hotel en el que hace las reservas el cliente tipo de la base de datos.
El viajero que muestran los datos mayoritariamente se aloja en hoteles de ciudad. Otro argumento más que soporta la tesis de un viajero mayoritariamente de negocios.
Estadísticas promedio del viajero de negocios representado en la base de datos.
Los datos sugieren un viajero de negocios -viaja solo-, mayoritariamente aloja en estadías cortas -menos de 3 noches en días de semana- y en habitaciones básicas, sin demasiados servicios adicionales; y en hoteles de ciudad. Su gasto medio por noche es de app USD 100.
El viajero de los datos es frecuente ?. (0=No, 1=Si)
Los datos muestran que el viajero es nuevo para las cadenas de hoteles. Esto porque en su gran mayoría (97%) no es frecuente.
Historial de cancelaciones del viajero de negocios de la base de datos.
Adicionalmente, la persona que reserva noches de hotel no tiene un historial de cancelaciones previas, lo cual es consistente con el gráfico anterior respecto de que la persona no es un viajero frecuente.
Segmento de mercado - tipo de pago por la reserva de habitación.
Desde el punto de vista del negocio hotelero, el viajero medio de la base de datos es un pasajero nuevo, sin historial de reservas ni cancelaciones previas. Realiza sus reservas por medio de terceros (Agencias de viaje u operadores turísticos) y por lo general, realiza algún pago para garantizar su reserva.
Perfil de Check inn de los viajeros en la base de datos.
Por lo general, los mayores ingresos se producen en meses de verano del hemisferio norte -Agosto y Julio-. Mayo también es un mes con altos ingresos de reservas. Respecto del día del mes, no hay un patrón claro. Sin embargo, se ve una reducción en los ingresos de reservas a mediados de mes. No tenemos mayor información para explicar este hecho.
Apertura de reservas por año y status de la misma.
Datos muestran un crecimiento importante en las reservas realizadas en periodo 2016/2015 (+157%), mientras que una caída de un 29% entre 2017/2016. No tenemos información para explicar este movimiento. Las cancelaciones, por el contrario, se muestran estables en torno al 35% del total de reservas.
Estadía media de las personas que reservan habitaciones en la base de datos.
Hay una clara diferencia entre estadías de acuerdo al tipo de hotel. Los resorts, en promedio tienen una estancia media de +1día respecto de los hoteles de ciudad. El cliente que más se hospeda, es el cliente con tarifa corporativa, quedándose aproximadamente 6 días en resorts. Es este alojamiento canje de puntos?
Detalle del status de las reservas - Variable Objetivo.
Problema de clasificación multiclase y claro desbalanceo en la variable objetivo. Clase mayoritaria -Check_out- concentra el 63% de las observaciones. Las cancelaciones de reservas representan el 36% de los datos. En la práctica, veremos alternativa de realizar proceso de “downsample” como opción en la grilla de control de Caret para tratar de corregir el problema.
Cancelaciones por tipo de cliente y año.
Existe algo con los datos de 2015?. Hay algún hecho que explique el salto entre 2015/2016?. Con los datos que tenemos, no podemos dar respuestas. Las cancelaciones son consistentes con el tipo de viajero en la base: mayoritariamente de negocios.
Cancelaciones por tipo de cliente y país.
Las cancelaciones por país también son consistentes con la distribución de los países en la muestra, con la excepción de Italia, que en el ranking de países aparece más abajo del lugar 10, mientras que en cancelaciones es top 5. Son más exigentes los Italianos ?, son más sensibles al precio?.
Cancelaciones por tipo de hotel y segmento de mercado.
Tarifas medias por cliente y status de reserva.
Los datos muestran que los clientes que más cancelaron reservas de hotel son los viajeros de estadías cortas que viajan solos. Estos clientes enfrentaron tarifas de app +USD 30 que los clientes que tienen tarifas corporativas, y app +USD 18 que los grupos de viajeros. Dentro del grupo de viajeros solos, la diferencia de tarifa entre quienes cancelaron y los que no es de app +USD 6. Es el precio un determinante en la cancelación de reservas?.
Perfil de cancelaciones por mes y día del mes.
Como indicamos las gráficas de cancelaciones son muy similares a las que muestran las gráficas de check-in; es decir, altas cancelaciones en meses de verano en hemisferio norte -Agosto y Julio-, y adicionalmente, Mayo. Respecto del día, no hay patrón claro, sin embargo, se ve la misma baja en cancelaciones a mediados de mes.
#=========================================================================
# PREPARACION PRELIMINAR
#=========================================================================
# Lectura del set de datos previamente trabajado en Excel:
datos<-read.csv2("hotel_bookings.csv")
# Cargamos conjunto de funciones y librerías necesarias:
source("Funciones_R.R")
library(dummies)
library(questionr)
library(psych)
library(car)
library(corrplot)
library(caret)
library(lubridate)
library(data.table)
#=========================================================================
# DEPURACION DE DATOS
#=========================================================================
# Revisamos variables mal categorizadas y corregimos:
# 1) Revisamos las variables numéricas para ver aquellas que deberían ser factor:
sapply(Filter(is.numeric, datos),function(x) length(unique(x)))
# Variables que deben pasar a factor:"is_canceled", "arrival_date_year",
# "arrival_date_month", "arrival_date_day_of_month" , "is_repeated_guest",
# "agent", "company":
datos[,c(2,4,5,6,7,20,27,28)] <- lapply(datos[,c(2,4,5,6,7,20,27,28)], factor)
# 2) Las variables arrival_date & reservation_status_date las pasamos a formato fecha:
datos$fe_fechaArrival <- dmy(datos$arrival_date)
datos$fe_fechaResStatus <- dmy(datos$reservation_status_date)
# Vemos las variables factor si hay categorías poco representadas:
freq(datos$hotel,sort = "dec")
freq(datos$is_canceled,sort = "dec")
freq(datos$arrival_date_year,sort = "dec")
freq(datos$arrival_date_month,sort = "dec")
freq(datos$arrival_date_day_of_month,sort = "dec")
freq(datos$meal,sort = "dec")
freq(datos$country,sort = "dec")
freq(datos$market_segment,sort = "dec")
freq(datos$distribution_channel,sort = "dec")
freq(datos$is_repeated_guest,sort = "dec")
freq(datos$reserved_room_type,sort = "dec")
freq(datos$assigned_room_type,sort = "dec")
freq(datos$deposit_type,sort = "dec")
freq(datos$agent,sort = "dec")
freq(datos$company,sort = "dec")
freq(datos$reservation_status,sort = "dec")
freq(datos$customer_type,sort = "dec")
# Agrupamos categorías bajo representadas:
datos$meal<-car::recode(datos$meal,"'No_Meal'='Other';
'Full_Board'='Other'
")
datos$market_segment<-car::recode(datos$market_segment,"'Direct'='Other';
'Corporate'='Other';
'Complementary'='Other';
'Aviation'='Other'
")
datos$distribution_channel<-car::recode(datos$distribution_channel,"'Corporate'='Other';
'Global_DistributionServ'='Other'
")
datos$reserved_room_type<-car::recode(datos$reserved_room_type,"'F'='Other';
'G'='Other';
'B'='Other';
'C'='Other';
'H'='Other';
'P'='Other';
'L'='Other'
")
datos$assigned_room_type<-car::recode(datos$assigned_room_type,"'B'='Other';
'H'='Other';
'I'='Other';
'K'='Other';
'P'='Other';
'L'='Other'
")
datos$deposit_type<-car::recode(datos$deposit_type,"'Non_Refund'='Deposit';
'Refundable'='Deposit'
")
datos$arrival_date_month<-car::recode(datos$arrival_date_month,"c('1','2','3')='Q1';
c('4','5','6')='Q2';
c('7','8','9')='Q3';
c('10','11','12')='Q4';
")
datos$arrival_date_day_of_month<-car::recode(datos$arrival_date_day_of_month,"
c('1','2','3','4','5','6','7','8',
'9','10','11','12','13','14','15')='H1';
c('16','17','18','19','20','21','22','23',
'24','25','26','27','28','29','30','31')='H2'
")
datos$customer_type<-car::recode(datos$customer_type,"'Contract'='Other';
'Group'='Other'
")
datos$company<-car::recode(datos$company,"'9999'='Unknown';
")
datos$agent<-car::recode(datos$agent,"'9999'='Unknown';
")
# Recatalogamos valores "Unknown" en variables factor
# con baja representación como missing (NA):
datos$meal<-recode.na(datos$meal,"Unknown")
datos$market_segment<-recode.na(datos$market_segment,"Unknown")
datos$distribution_channel<-recode.na(datos$distribution_channel,"Unknown")
# Recatalogamos valores "9999" en variables numéricas como missing (NA):
datos$meal<-recode.na(datos$meal,"9999")
datos$children<-recode.na(datos$children,"9999")
datos$Long_av<-recode.na(datos$Long_av,"9999")
datos$Lat_av<-recode.na(datos$Lat_av,"9999")
# Valores fuera de rango, en average daily rate hay valores negativos, esto no puede ser
# ellos pasan a NA's:
datos$Av_DailyRate<-replace(datos$Av_DailyRate, which((datos$Av_DailyRate < 0)), NA)
# Cambiamos a numeric todas las variables que no son factor
datos[,c(3,8:13,16,17,21:22,25,29,31:33)<-lapply(datos[,
c(3,8:13,16,17,21:22,25,29,31:33)],
as.numeric)
# Separamos el set de datos para poder imputar missings & outliers
varObj<-datos$reservation_status
input<-as.data.frame(datos[,-34])
noImputar<-input[,c(11,12,13,21,22,25,29,32,33)]
imputar<-input[,-c(11,12,13,21,22,25,29,32,33)]
#=========================================================================
# Tratamiento de missings & outliers
#=========================================================================
# Revisamos la cantidad de outlyers por variable:
sapply(Filter(is.numeric, imputar),function(x) atipicosAmissing(x)[[2]])/nrow(imputar)
# El % de atipicos por variables numéricas está bajo del 1%. Por lo tanto, recatalogamos
# a missings para luego imputar:
imputar[,as.vector(which(sapply(imputar, class)=="numeric"))]<-sapply(
Filter(is.numeric, imputar),function(x) atipicosAmissing(x)[[1]])
# Revisamos la cantidad de missings por variable:
imputar$prop_missings<-apply(is.na(imputar),1,mean) # Por observación
summary(imputar$prop_missings) # Max % es un 14%, es aceptable para imputar
(prop_missingsVars<-apply(is.na(imputar),2,mean)) # Por variable
# Verificamos que el % de missings por variable es bajo, por lo tanto, imputamos:
# Imputo todas las cuantitativas por mediana
imputar[,as.vector(which(sapply(imputar, class)=="numeric"))]<-sapply(
Filter(is.numeric, imputar),function(x) ImputacionCuant(x,"media"))
# Imputo todas las cualitativas por moda
imputar[,as.vector(which(sapply(imputar, class)=="factor"))]<-sapply(
Filter(is.factor, imputar),function(x) ImputacionCuali(x,"moda"))
# Revisamos que no haya variables "char"
imputar[,as.vector(which(sapply(imputar, class)=="character"))] <- lapply(
imputar[,as.vector(which(sapply(imputar, class)=="character"))] , factor)
# Verificamos que no queden valores missings
anyNA(imputar)
# Imputamos valores missings en variable children
noImputar$children<-ImputacionCuant(noImputar$children,"media")
#=========================================================================
# Formación del set de datos limpio
#=========================================================================
datosLimpios<-cbind(imputar,noImputar,varObj)
# Guardamos set de datos limpios para Feature engineering
saveRDS(datosLimpios,"datosLimpios")
# Realizamos carga de librerías
library(inspectdf)
library(corrplot)
library(ggplot2)
library(questionr)
library(psych)
library(car)
library(lubridate)
library(dummies)
library(caret)
source("Funciones_R.R")
# Realizamos carga de datos
datosLimpios<-readRDS("datosLimpios")
#=========================================================================
# CREACIÓN DE VARIABLES
#=========================================================================
# De las variables Long_av y Lat_av creamos una variable distancia
datosLimpios$fe_longLat<-sqrt(datosLimpios$Long_av^2+datosLimpios$Lat_av^2)
# Determinamos fecha de cuándo se hizo la reserva:
datosLimpios$fe_fechaReserva<-(datosLimpios$fe_fechaArrival-datosLimpios$lead_time)
# Determinamos día de semana cuándo se hizo la reserva
datosLimpios$fe_diaSemanaReserva<-wday(datosLimpios$fe_fechaReserva)
# Determinamos día se semana de llegada a alojamiento
datosLimpios$fe_diaSemanaLlegada<-wday(datosLimpios$fe_fechaArrival)
# Determinamos el total de factura a pagar por la estadía. Supuestos
datosLimpios$fe_totalFactura<-((datosLimpios$stays_in_weekend_nights+
datosLimpios$stays_in_week_nights)*
datosLimpios$Av_DailyRate*1.3
)
#=========================================================================
# TRANSFORMACION DE VARIABLES
#=========================================================================
# Variables factor con demasiadas categorías, y que no pudieron ser agrupadas
# anteriormente, pasan a numéricas por su frecuencia). Variables originales,
# se eliminan.
# agente:
datosLimpios$fe_agent<-unname(table(datosLimpios$agent)[datosLimpios$agent])
# country
datosLimpios$fe_country<-unname(table(datosLimpios$country)[datosLimpios$country])
# company
datosLimpios$fe_company<-unname(table(datosLimpios$company)[datosLimpios$company])
# Pasamos a numéricas estas variables:
datosLimpios[,c(44:46)] <- lapply(datosLimpios[,c(44:46)], as.numeric)
#=========================================================================
# ELIMINACION DE VARIABLES
#=========================================================================
# Las siguientes variables no se usarán -> se eliminan del set de datos:
datosLimpios$arrival_date<-NULL # Por feature engineering
datosLimpios$reservation_status_date<-NULL # Por feature engineering
datosLimpios$prop_missings<-NULL # No aporta información
datosLimpios$is_canceled<-NULL # No aporta información
datosLimpios$Long_av<-NULL # Por correlación alta
datosLimpios$Lat_av<-NULL # Por correlación alta
datosLimpios$agent<-NULL # Por feature engineering
datosLimpios$country<-NULL # Por feature engineering
datosLimpios$company<-NULL # por feature engineering
datosLimpios$fe_fechaArrival<-NULL # No aporta información
datosLimpios$fe_fechaReserva<-NULL # No aporta información
datosLimpios$fe_fechaResStatus<-NULL # No aporta información
# Aplicamos logaritmos a variables asimétricas
datosTransf<-datosLimpios
datosTransf$fe_avDailyRate<-(log(datosTransf$Av_DailyRate))
datosTransf$fe_leadTime<-(log(datosTransf$lead_time))
datosTransf$fe_weekNights<-(log(datosTransf$stays_in_week_nights))
datosTransf$fe_weekendNights<-(log(datosTransf$stays_in_weekend_nights))
datosTransf$fe_specialRequest<-(log(datosTransf$total_of_special_requests))
datosTransf$fe2_totalFactura<-(log(datosTransf$fe_totalFactura))
datosTransf$Av_DailyRate<-NULL
datosTransf$lead_time<-NULL
datosTransf$stays_in_week_nights<-NULL
datosTransf$stays_in_weekend_nights<-NULL
datosTransf$total_of_special_requests<-NULL
datosTransf$fe_totalFactura<-NULL
datosTransf$fe_adults<-NULL
datosTransf$fe_babies<-NULL
# Revisamos gráficamente
eda <- inspect_num(datosTransf)
show_plot(eda)
#=========================================================================
# ESTANDARIZACIÓN Y DUMMIES
#=========================================================================
# SET DE DATOS 1
# Creación de dummies
colnames(Filter(is.factor,datosLimpios))
varFactor1<-c("hotel", "arrival_date_year", "arrival_date_month",
"arrival_date_day_of_month", "meal", "market_segment",
"distribution_channel", "is_repeated_guest", "reserved_room_type",
"assigned_room_type", "deposit_type", "customer_type")
# Variables factor pasan a dummies:
inputDummies<- dummy.data.frame(datosLimpios, varFactor1, sep = ".")
# Estandarización de variables numéricas
colnames(Filter(is.numeric,datosLimpios))
varNumeric1<-c("lead_time", "arrival_date_week_number", "stays_in_weekend_nights",
"stays_in_week_nights", "Av_DailyRate", "adults", "children", "babies",
"previous_cancellations", "previous_bookings_not_canceled",
"booking_changes", "days_in_waiting_list", "required_car_parking_spaces",
"total_of_special_requests", "fe_longLat", "fe_diaSemanaReserva",
"fe_diaSemanaLlegada", "fe_totalFactura", "fe_agent", "fe_country",
"fe_company")
means <-apply(inputDummies[,varNumeric1],2,mean)
sds<-sapply(inputDummies[,varNumeric1],sd)
contSD<-scale(inputDummies[,varNumeric1], center = means, scale = sds)
contExcluir<-which(colnames(inputDummies)%in%varNumeric1)
# Construimos el set de datos final
datosFinal<-cbind(inputDummies[,-contExcluir],contSD)
# Guardamos los datos:
saveRDS(datosFinal,"datosFinal")
# Realizamos partición de datos 70%, 20% 10%
# 70% para train
train <- createDataPartition(y = datosFinal$varObj, p = 0.70, list = FALSE, times = 1)
datosTrain<-datosFinal[train,]
saveRDS(datosTrain,"datosTrain")
# 20% para validación
aux<-datosFinal[-train,]
validacion<-createDataPartition(y = aux$varObj, p = 0.6667, list = FALSE, times = 1)
datosValidacion<-aux[validacion,]
saveRDS(datosValidacion,"datosValidacion")
# 10% para test
datosTest<-aux[-validacion,]
saveRDS(datosTest,"datosTest")
# SET DE DATOS 2:
# Construimos segundo set de datos
colnames(Filter(is.factor,datosTransf))
varFactor2<-c("hotel", "arrival_date_year", "arrival_date_month",
"arrival_date_day_of_month", "meal", "market_segment",
"distribution_channel", "is_repeated_guest", "reserved_room_type",
"assigned_room_type", "deposit_type", "customer_type"
)
# Variables factor pasan a dummies:
inputDummies2<- dummy.data.frame(datosTransf, varFactor2, sep = ".")
# Estandarización de variables numéricas
colnames(Filter(is.numeric,datosTransf))
varNumeric2<-c("arrival_date_week_number", "adults", "children", "babies",
"previous_cancellations", "previous_bookings_not_canceled",
"booking_changes", "days_in_waiting_list", "required_car_parking_spaces",
"fe_longLat", "fe_diaSemanaReserva", "fe_diaSemanaLlegada", "fe_agent",
"fe_country", "fe_company"
)
means2 <-apply(inputDummies2[,varNumeric2],2,mean)
sds2<-sapply(inputDummies2[,varNumeric2],sd)
contSD2<-scale(inputDummies2[,varNumeric2], center = means2, scale = sds2)
contExcluir2<-which(colnames(inputDummies2)%in%varNumeric2)
# Construimos el set de datos final
datosFinal2<-cbind(inputDummies2[,-contExcluir2],contSD2)
# Verificamos datos perdidos en el set de datos
anyNA(datosFinal2)
# Guardamos los datos:
saveRDS(datosFinal2,"datosFinal2")
# Realizamos partición de datos 70%, 20% 10%
train2 <- createDataPartition(y = datosFinal2$varObj, p = 0.70, list = FALSE, times = 1)
datosTrain2<-datosFinal2[train2,]
saveRDS(datosTrain2,"datosTrain2")
# 20% para validación
aux2<-datosFinal2[-train2,]
validacion2<-createDataPartition(y = aux2$varObj, p = 0.6667, list = FALSE, times = 1)
datosValidacion2<-aux2[validacion2,]
saveRDS(datosValidacion2,"datosValidacion2")
# 10% para test
datosTest2<-aux2[-validacion2,]
saveRDS(datosTest2,"datosTest2")
Diagrama de correlaciones entre variables numéricas.
De la gráfica de correlaciones, podemos ver que las variables no presentan una correlación alta entre ellas -mayor al 70%-, con excepción de la variable fe_longLat vs Long_av y Lat_av, que son las variables que la formaron. Estas 2 últimas variables se eliminan del set de datos. La otra variable que muestra una correlación relativamente elevada es fe_totalFactura con sus variables componentes. En este caso la correlación no es elevada, por lo que se mantiene en el set de datos.
Histogramas de variables numéricas previa transformación ln().
Gráficas muestran los histogramas de las distribuciones de las variables antes del proceso de transformación aplicada.
Histogramas de variables numéricas luego de transformación ln().
Gráficas muestran los histogramas de las distribuciones de las variables después del proceso de transformación aplicada. Puede observarse variables más centradas.
| # | Variable | MeanDecreaseAccuracy | % | % Acum | Seleccionada? |
|---|---|---|---|---|---|
| 1 | fe_country | 54.791 | 9,0% | 9,0% | si |
| 2 | lead_time | 49.011 | 8,0% | 17,0% | si |
| 3 | total_of_special_requests | 43.870 | 7,2% | 24,2% | si |
| 4 | fe_agent | 43.294 | 7,1% | 31,3% | si |
| 5 | fe_longLat | 41.235 | 6,8% | 38,1% | si |
| 6 | deposit_type.Deposit | 31.837 | 5,2% | 43,3% | si |
| 7 | deposit_type.No_Deposit | 31.636 | 5,2% | 48,5% | si |
| 8 | fe_totalFactura | 22.526 | 3,7% | 52,2% | si |
| 9 | market_segment.Online_TA | 21.526 | 3,5% | 55,7% | si |
| 10 | Av_DailyRate | 20.366 | 3,3% | 59,1% | si |
| 11 | previous_cancellations | 18.996 | 3,1% | 62,2% | si |
| 12 | customer_type.Transient | 15.692 | 2,6% | 64,8% | si |
| 13 | assigned_room_type.A | 14.293 | 2,3% | 67,1% | si |
| 14 | arrival_date_week_number | 13.644 | 2,2% | 69,4% | si |
| 15 | required_car_parking_spaces | 13.135 | 2,2% | 71,5% | si |
| 16 | customer_type.Transient_Party | 13.004 | 2,1% | 73,7% | si |
| 17 | arrival_date_year.2015 | 11.297 | 1,9% | 75,5% | si |
| 18 | stays_in_week_nights | 10.685 | 1,8% | 77,3% | si |
| 19 | booking_changes | 8.528 | 1,4% | 78,7% | si |
| 20 | arrival_date_year.2017 | 8.158 | 1,3% | 80,0% | si |
| 21 | arrival_date_year.2016 | 7.702 | 1,3% | 81,3% | si |
| 22 | market_segment.Groups | 7.375 | 1,2% | 82,5% | si |
| 23 | distribution_channel.TATO | 6.997 | 1,1% | 83,6% | si |
| 24 | hotel.Resort_Hotel | 6.932 | 1,1% | 84,8% | si |
| 25 | market_segment.Other | 6.873 | 1,1% | 85,9% | si |
| 26 | hotel.City_Hotel | 6.666 | 1,1% | 87,0% | si |
| 27 | stays_in_weekend_nights | 6.581 | 1,1% | 88,1% | si |
| 28 | fe_diaSemanaLlegada | 6.362 | 1,0% | 89,1% | si |
| 29 | market_segment.Offline_TATO | 6.318 | 1,0% | 90,1% | si |
| 30 | reserved_room_type.A | 6.070 | 1,0% | 91,1% | si |
| 31 | fe_diaSemanaReserva | 5.247 | 0,9% | 92,0% | no |
| 32 | assigned_room_type.D | 4.672 | 0,8% | 92,8% | no |
| 33 | adults | 4.537 | 0,7% | 93,5% | no |
| 34 | distribution_channel.Direct | 3.147 | 0,5% | 94,0% | no |
| 35 | arrival_date_month.Q3 | 3.093 | 0,5% | 94,5% | no |
| 36 | meal.Bed_Breakfast | 2.838 | 0,5% | 95,0% | no |
| 37 | arrival_date_month.Q2 | 2.779 | 0,5% | 95,5% | no |
| 38 | arrival_date_month.Q4 | 2.642 | 0,4% | 95,9% | no |
| 39 | arrival_date_month.Q1 | 2.567 | 0,4% | 96,3% | no |
| 40 | reserved_room_type.D | 2.386 | 0,4% | 96,7% | no |
| 41 | meal.Half_Board | 2.036 | 0,3% | 97,0% | no |
| 42 | previous_bookings_not_canceled | 1.943 | 0,3% | 97,3% | no |
| 43 | arrival_date_day_of_month.H2 | 1.941 | 0,3% | 97,7% | no |
| 44 | arrival_date_day_of_month.H1 | 1.870 | 0,3% | 98,0% | no |
| 45 | meal.Other | 1.868 | 0,3% | 98,3% | no |
| 46 | children | 1.573 | 0,3% | 98,5% | no |
| 47 | fe_company | 1.508 | 0,2% | 98,8% | no |
| 48 | distribution_channel.Other | 1.161 | 0,2% | 99,0% | no |
| 49 | is_repeated_guest.1 | 868 | 0,1% | 99,1% | no |
| 50 | is_repeated_guest.0 | 854 | 0,1% | 99,3% | no |
| 51 | reserved_room_type.Other | 834 | 0,1% | 99,4% | no |
| 52 | days_in_waiting_list | 814 | 0,1% | 99,5% | no |
| 53 | customer_type.Other | 657 | 0,1% | 99,6% | no |
| 54 | assigned_room_type.E | 633 | 0,1% | 99,7% | no |
| 55 | reserved_room_type.E | 446 | 0,1% | 99,8% | no |
| 56 | assigned_room_type.Other | 428 | 0,1% | 99,9% | no |
| 57 | assigned_room_type.F | 422 | 0,1% | 100,0% | no |
| 58 | assigned_room_type.C | 167 | 0,0% | 100,0% | no |
| 59 | assigned_room_type.G | 109 | 0,0% | 100,0% | no |
| 60 | babies | 3 | 0,0% | 100,0% | no |
| — | ———————————————— | ——————— | ——– | ——– | ————— |
| TOTAL | 609.443 | 100,0% |
Cuadro muestra el output de la función variableImportance() de la librería Random Forest, para el modelo 4. Segunda columna muestra el nombre de la variable del set de datosLimpios. Las variables están ordenadas de forma decreciente por su efecto negativo sobre el accuracy. (MeanDecreaseAccuracy). Se seleccionaron aquellas variables que acumulaban un efecto en el accuracy de un 90% (5a columna). Última columna booleana indica si la variable fue seleccionada para futuros modelos o no. Se seleccionaron 30 variables (número redondo).
#=========================================================================
# Modelo Random Forest
#=========================================================================
# Realizamos carga de librerías a utilizar en la práctica
library(caret)
library(randomForest)
library(questionr)
library(pROC)
# Realizamos carga de los sets de datos
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")
#-------------------------------------------------------------------------
# Modelo 1: Tuning de hiperparámetros: # de árboles
#-------------------------------------------------------------------------
# En grilla de hiperparamétros incluimos opción de downsample para manejar
# problema de desbalanceo en variable objetivo.
control<-trainControl(method = "cv",
number=4,
savePredictions = "all",
sampling="down",
classProbs=TRUE)
# Probamos con mtry = sqrt(n variables)
rfgrid <- expand.grid(mtry = 8)
modellist <- list() # creamos lista vacía
# Probamos 4 tamaños de árbol y 1 semilla
for (ntree in c(1000,1500,2000,2500)){
set.seed(1234)
# Entrenamos modelo
rf1<- train(factor(varObj)~., # Usamos las 60 variables
data=datosTrain,
method="rf",
trControl=control,
tuneGrid=rfgrid,
linout = FALSE,
ntree=ntree,
nodesize=10,
replace=TRUE,
importance=TRUE)
key <- toString(ntree)
modellist[[key]] <- rf1
}
# Obtenemos los resultados. Buen modelo con 2.000 árboles.
results <- resamples(modellist)
summary(results)
#-------------------------------------------------------------------------
# Modelo 2: Vemos si mejora accuracy con número distinto de árboles
#-------------------------------------------------------------------------
for (ntree in c(500,2500,3000,3500,4000,5000)){
set.seed(1234) # Misma semilla
rf2<- train(factor(varObj)~., # Usamos las 60 variables
data=datosTrain,
method="rf",
trControl=control,
tuneGrid=rfgrid,
linout = FALSE,
ntree=ntree,
nodesize=10,
replace=TRUE,
importance=TRUE)
key <- toString(ntree)
modellist[[key]] <- rf2
}
# Mostramos resultados.
results2 <- resamples(modellist)
summary(results2)
#-------------------------------------------------------------------------
# Modelo 3, tuneado de mtry
#-------------------------------------------------------------------------
# Verificamos que el mtry escogido = 8 es bueno
rfgrid <- expand.grid(mtry = c(3,4,5,6,7,8,9,10))
set.seed(1234) # Misma semilla para mantener muestra
# Entrenamos el modelo
rf3<- train(factor(varObj)~., # Usamos las 60 variables
data=datosTrain,
method="rf",
trControl=control,
tuneGrid=rfgrid,
linout = FALSE,
ntree=2000,
nodesize=10,
replace=TRUE,
importance=TRUE)
# Creamos subset de datos del mejor modelo, dado mtry
datos3<-subset(rf3$pred,mtry==8)
# Mostramos matriz de confusión para el mejor modelo:
confusionMatrix(datos3$pred,datos3$obs)
# Calculamos AUC
# roc1<-multiclass.roc(as.numeric(rf3$finalModel$y),as.numeric(rf3$finalModel$predicted))
# roc1$auc
# Realizamos predicciones con modelo downsample, 2.000 arboles y Mtry = 8
p1<-predict(rf3,datosValidacion[,-40])
# Creamos Matriz de confusión
confusionMatrix(datosValidacion$varObj,p1)
# Calculamos AUC:
# roc2<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(p1))
# roc2$auc
#-------------------------------------------------------------------------
# Modelo 4: Probamos sin opción de sampling con 2.000 arboles y mtry = 8
#-------------------------------------------------------------------------
# Modificamos grilla de control en opción sampling
control<-trainControl(method = "cv",
number=4,
savePredictions = "all",
classProbs=TRUE)
rfgrid <- expand.grid(mtry = 8)
set.seed(1234) # Misma semilla
# Entrenamos el modelo
rf4<- train(factor(varObj)~., # Usamos las 60 variables
data=datosTrain,
method="rf",
trControl=control,
tuneGrid=rfgrid,
linout = FALSE,
ntree=2000,
nodesize=10,
replace=TRUE,
importance=TRUE)
# Mostramos matriz de confusión con resultados:
confusionMatrix(rf4$pred$pred,rf4$pred$obs)
# roc3<-multiclass.roc(as.numeric(rf4$finalModel$y),as.numeric(rf4$finalModel$predicted))
# roc3$auc
# Realizamos predicciones para el modelo sobre set de validación
p2<-predict(rf4,datosValidacion[,-40])
# Creamos Matriz de confusión
confusionMatrix(datosValidacion$varObj,p2)
# Calculamos AUC:
# roc4<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(p2))
# roc4$auc
# Obtenemos las variables mas importantes
rf4$finalModel$importance
# Las variables más importantes:
mejVariables<-c("fe_country","lead_time","total_of_special_requests","fe_agent",
"fe_longLat","deposit_type.Deposit","deposit_type.No_Deposit","fe_totalFactura",
"market_segment.Online_TravelAgents","Av_DailyRate","previous_cancellations",
"customer_type.Transient","assigned_room_type.A","arrival_date_week_number",
"required_car_parking_spaces","customer_type.Transient_Party",
"arrival_date_year.2015","stays_in_week_nights","booking_changes",
"arrival_date_year.2017","arrival_date_year.2016","market_segment.Groups",
"distribution_channel.TravAgents_TurismOperator","hotel.Resort_Hotel",
"market_segment.Other","hotel.City_Hotel","stays_in_weekend_nights",
"fe_diaSemanaLlegada","market_segment.Offline_TravAgentsTurOperators",
"reserved_room_type.A")
# paste(mejVariables,collapse="+")
#-------------------------------------------------------------------------
# Modelo 5, 2.000 árboles, mtry cambia a 6. Solo variables más importantes.
#-------------------------------------------------------------------------
# Grilla de control la mantenemos sin opción sampling
control<-trainControl(method = "cv",
number=4,
savePredictions = "all",
classProbs=TRUE)
# Modificamos mtry por menor número de variables
rfgrid <- expand.grid(mtry = 6)
set.seed(1234) # Misma semilla
# Entrenamos el modelo
rf5<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+
deposit_type.No_Deposit+fe_totalFactura+
market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+
assigned_room_type.A+arrival_date_week_number+
required_car_parking_spaces+customer_type.Transient_Party+
arrival_date_year.2015+stays_in_week_nigts+
booking_changes+arrival_date_year.2017+arrival_date_year.2016+
market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+
hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
stays_in_weekend_nights+fe_diaSemanaLlegada+
market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data=datosTrain,
method="rf",
trControl=control,
tuneGrid=rfgrid,
linout = FALSE,
ntree=2000,
nodesize=10,
replace=TRUE,
importance=TRUE)
# Vemos resultados
confusionMatrix(rf5$pred$pred,rf5$pred$obs)
# Calculamos AUC
# roc5<-multiclass.roc(as.numeric(rf5$finalModel$y),as.numeric(rf5$finalModel$predicted))
# roc5$auc
# Realizamos predicciones sobre el set de datos validación
p3<-predict(rf5,datosValidacion[,-40])
# Creamos Matriz de confusión
confusionMatrix(datosValidacion$varObj,p3)
# Calculamos AUC:
# roc6<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(p3))
# roc6$auc
#-------------------------------------------------------------------------
# Validación cruzada repetida modelo tuneado
#-------------------------------------------------------------------------
# Cambiamos semilla para ver una muestra distinta
set.seed(4321)
# Definimos grilla de control
control<-trainControl(method = "repeatedcv",
number=10,
repeats=10,
savePredictions = "all",
classProbs=TRUE)
rfgrid <-expand.grid(mtry=6)
# Entrenamos el modelo
modRandomForest<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+
deposit_type.No_Deposit+fe_totalFactura+
market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+
assigned_room_type.A+arrival_date_week_number+
required_car_parking_spaces+customer_type.Transient_Party+
arrival_date_year.2015+stays_in_week_nigts+
booking_changes+arrival_date_year.2017+arrival_date_year.2016+
market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+
hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
stays_in_weekend_nights+fe_diaSemanaLlegada+
market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data=datosTrain,
method="rf",
trControl=control,
tuneGrid=rfgrid,
linout = FALSE,
ntree=2000,
nodesize=10,
sampsize=2000,
replace=TRUE
)
# Revisamos performance con matriz de confusión
confusionMatrix(modRandomForest$pred$pred,modRandomForest$pred$obs)
preditest<-modRandomForest$pred
# Chequeamos que resultados se mantengan
confusionMatrix(preditest$pred,preditest$obs)
preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL
# Definimos función de cálculo de Accuracy:
tasaAciertos<-function(x,y) {
confu<-confusionMatrix(x,y)
tasa<-confu[[3]][1]
return(tasa)
}
# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
paso1<-preditest[which(preditest$Rep==repi),]
acc=tasaAciertos(paso1$pred,paso1$obs)
medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"
# Guardamos datos de validación cruzada
saveRDS(medias,"modRandomForest")
Output modelo Random Forest 1.
Modelo 1 de Random Forest buscaba la determinación de la mejor combinación de hiperparámetros. Se puede ver que el accuracy medio no varia demasiado con el número de árboles; sin embargo, aumenta en la medida que éstos lo hacen. El valor fluctúa en torno a los 70,33% y 70,38% -recordar que este modelo tenía incorporada la opción “downsample”. Un segundo modelo amplía la búsqueda para ver qué ocurre con más árboles.
Output modelo Random Forest 2.
Un segundo modelo Random Forest amplió la búsqueda incorporando más árboles a los modelos. Se puede ver que el accuracy aumenta con un mayor número de árboles, pero que este aumento no es importante, por lo que no vale la pena complejizar el modelo por una mejora muy marginal del accuracy. Selección final fue por 2.000 árboles.
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 21.714 | 5.325 | 79 | 27.118 |
| Check_Out | 3.807 | 37.412 | 146 | 41.365 |
| No_Show | 4.591 | 9.880 | 620 | 15.091 |
| Total | 30.112 | 52.617 | 845 | 83.574 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 59.746 | |||
| Accuracy | 71,5% |
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 6.385 | 852 | 1.367 | 8.604 |
| Check_Out | 1.838 | 10.394 | 2.802 | 15.034 |
| No_Show | 24 | 29 | 189 | 242 |
| Total | 8.247 | 11.275 | 4.358 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 16.968 | |||
| Accuracy | 71,1% |
#=========================================================================
# Modelo Multinomial
#=========================================================================
# Realizamos carga de librerías a utilizar en la práctica
library(caret)
library(questionr)
library(pROC)
# Realizamos carga de los sets de datos
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")
#=========================================================================
# Modelo 1 30 variables importantes de Random Forest
#=========================================================================
# Definimos la grilla de control
control<-trainControl(method = "cv",
number=4,
savePredictions = "all",
classProbs=TRUE)
# Modelo
set.seed(1234) # Misma semilla que Random Forest
multinom1 <- train(varObj~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+
deposit_type.No_Deposit+fe_totalFactura+
market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+
assigned_room_type.A+arrival_date_week_number+
required_car_parking_spaces+customer_type.Transient_Party+
arrival_date_year.2015+stays_in_week_nights+booking_changes+
arrival_date_year.2017+arrival_date_year.2016+
market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+
hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
stays_in_weekend_nights+fe_diaSemanaLlegada+
market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data = datosTrain,
method = "multinom",
trControl = control
)
# Revisamos resultados
multinom1
a<-subset(multinom1,multinom1$pred$decay==0)
a<-as.data.frame(a[5]$pred)
# Calculamos matriz de confusión para mejor modelo, decay =0
confusionMatrix(a$pred,a$obs)
# Realizamos predicciones sobre el set de validación
p1<-predict(multinom1,datosValidacion[,-40])
# Vemos la matriz de confusión
confusionMatrix(p1,datosValidacion$varObj)
#-------------------------------------------------------------------------
# Validación cruzada repetida
#-------------------------------------------------------------------------
set.seed(4321) # Cambiamos semilla para ver muestra distinta
control<-trainControl(method = "repeatedcv",
number=10,
repeats=10,
savePredictions = "all",
classProbs=TRUE)
# Corremos modelo
multinom2 <- train(varObj~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+
deposit_type.No_Deposit+fe_totalFactura+
market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+
assigned_room_type.A+arrival_date_week_number+
required_car_parking_spaces+customer_type.Transient_Party+
arrival_date_year.2015+stays_in_week_nights+booking_changes+
arrival_date_year.2017+arrival_date_year.2016+
market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+
hotel.Resort_Hotel+market_segment.Other+hotel.City_Hotel+
stays_in_weekend_nights+fe_diaSemanaLlegada+
market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data = datosTrain,
method = "multinom",
trControl = control
)
# Rescatamos las predicciones como data frame
multinom2$pred
preditest<-multinom2$pred
# Obtenemos las predicciones del mejor modelo entrenado
a<-subset(preditest,preditest$decay==1e-04)
# Matriz de confusión del mejor modelo
confusionMatrix(a$pred,a$obs)
# Construimos tabla para accuracy por repetición
preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL
# Funcion de claculo de accuracy
tasaAciertos<-function(x,y) {
confu<-confusionMatrix(x,y)
tasa<-confu[[3]][1]
return(tasa)
}
# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
paso1<-preditest[which(preditest$Rep==repi),]
acc=tasaAciertos(paso1$pred,paso1$obs)
medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"
# Revisamos los resultados
medias
# Guardamos los datos de los accuracy
saveRDS(medias,"modMultinomial")
#=========================================================================
# Modelo XGboost
#=========================================================================
# Cargamos librerías necesarias
library(caret)
library(pROC)
# Realizamos carga de datos:
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")
datosTest<-readRDS("datosTest")
#------------------------------------------------------------------------
# Modelo Xgboost1: tuneado de hiperparámetros
#------------------------------------------------------------------------
# Definimos grilla de hiperparámetros a definir
xgbmgrid<-expand.grid(min_child_weight=c(5,10,15,20),
eta=c(0.1,0.05,0.03,0.01,0.001),
nrounds=c(100,500,1000,5000),
max_depth=6,
gamma=0,
colsample_bytree=1,
subsample=1)
# Definimos grilla de control, misma que en algoritmos anteriores
control<-trainControl(method = "cv",
number=4,
savePredictions = "all",
classProbs=TRUE)
set.seed(1234) # Misma semilla con la que hemos trabajado
# Entrenamos modelo
xgbm1<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+assigned_room_type.A+
arrival_date_week_number+required_car_parking_spaces+
customer_type.Transient_Party+arrival_date_year.2015+
stays_in_week_nights+booking_changes+arrival_date_year.2017+
arrival_date_year.2016+market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data=datosTrain,
method="xgbTree"
,trControl=control,
tuneGrid=xgbmgrid,
verbose=FALSE)
# Revisamos los resultados
xgbm1
# GRaficamos
plot(xgbm1)
#------------------------------------------------------------------------
# Modelo Xgboost2: Ajustamos iteraciones a rango 2.000 - 4.000
#------------------------------------------------------------------------
# Se redefine la grilla de hiperparámetros para ver que ocurre con los árboles
xgbmgrid<-expand.grid(min_child_weight=c(5),
eta=c(0.05),
nrounds=c(2000,3000,4000),
max_depth=6,
gamma=0,
colsample_bytree=1,
subsample=1)
# Mantenemos la grilla de control sin cambios
control<-trainControl(method = "cv",
number=4,
savePredictions = "all",
classProbs=TRUE)
set.seed(1234) # Misma semilla con la que hemos trabajado
# Entrenamos modelo
xgbm2<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+assigned_room_type.A+
arrival_date_week_number+required_car_parking_spaces+
customer_type.Transient_Party+arrival_date_year.2015+
stays_in_week_nights+booking_changes+arrival_date_year.2017+
arrival_date_year.2016+market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data=datosTrain,
method="xgbTree",
trControl=control,
tuneGrid=xgbmgrid,
verbose=FALSE)
# Revisamos gráficamente
plot(xgbm2)
# Revisamos resultados de modelo óptimo
mod2<-subset(xgbm2$pred,xgbm2$pred$nrounds==3000)
# Calculamos matriz de confusión
confusionMatrix(mod2$pred,mod2$obs)
# Calculamos AUC
# roc1<-multiclass.roc(as.numeric(mod2$pred),as.numeric(mod2$obs))
# roc1$auc
# Revisamos proyecciones sobre set de datos de validación
predXgb<-predict(xgbm2,datosValidacion[,-40])
# Calculamos matriz de confusión
confusionMatrix(datosValidacion$varObj,predXgb)
# Calculamos AUC
# roc2<-multiclass.roc(as.numeric(datosValidacion$varObj),as.numeric(predXgb))
# roc2$auc
#-------------------------------------------------------------------------
# Validación cruzada repetida modelo tuneado
#-------------------------------------------------------------------------
set.seed(4321) # Cambiamos semilla para ver una muestra distinta
# Creamos grilla de control
control<-trainControl(method = "repeatedcv",
number=10,
repeats=10,
savePredictions = "all",
classProbs=TRUE
)
# Definimos grilla de hiperparámetros igual al mejor modelo
xgbmgrid<-expand.grid(min_child_weight=c(5),
eta=c(0.05),
nrounds=c(3000),
max_depth=6,
gamma=0,
colsample_bytree=1,
subsample=1
)
# Entrenamos modelo
xgoost<- train(factor(varObj)~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+assigned_room_type.A+
arrival_date_week_number+required_car_parking_spaces+
customer_type.Transient_Party+arrival_date_year.2015+
stays_in_week_nights+booking_changes+arrival_date_year.2017+
arrival_date_year.2016+market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data=datosTrain,
method="xgbTree",
trControl=control,
tuneGrid=xgbmgrid,
verbose=FALSE
)
# Revisamos resultados con matriz de confusión
confusionMatrix(xgoost$pred$pred,xgoost$pred$obs)
preditest<-xgoost$pred
# Chequeamos que valores no hayan cambiado
confusionMatrix(preditest$pred,preditest$obs)
preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL
# Definimos función de accuracy:
tasaAciertos<-function(x,y) {
confu<-confusionMatrix(x,y)
tasa<-confu[[3]][1]
return(tasa)
}
# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
paso1<-preditest[which(preditest$Rep==repi),]
acc=tasaAciertos(paso1$pred,paso1$obs)
medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"
# Guardamos datos de validación cruzada
saveRDS(medias,"modXgboost")
#-------------------------------------------------------------------------
# Predicciones sobre el set de test
#-------------------------------------------------------------------------
# Realizamos predicciones sobre el set de test:
pred<-predict(xgbm2,datosTest[,-40])
# Calculamos matriz de confusión
confusionMatrix(datosTest$varObj,pred)
Hiperparámetros de modelo Xgboost1.
Como se comentó, el objetivo del primer modelo Xgboost fue determinar los mejores hiperparámetros para modelos posteriores. En la gráfica, podemos ver que un alto número de iteraciones -línea roja que corresponde a 5.000- domina en todos los escenarios. El máximo accuracy se produce con shrinkage de 0,05 y min_chil_weight de 5. existe una gran diferencia entre 1.000 a 5.000 iteraciones, por lo que el estudio de esta particular zona, para acotar el accuracy a un menor número de ellas fue el objetivo del modelo Xgboost 2.
Accuracy vs número de iteraciones en modelo Xgboost2.
Un segundo modelo Xgboost tuvo como objetivo, determinar el mejor número de árboles en el espacio 1.000 y 4.000 árboles. La gráfica muestra que el mejor accuracy se obtiene con 3.000 árboles.
#=========================================================================
# Modelo Multinomial
#=========================================================================
# Realizamos carga de librerías a utilizar en la práctica
library(caret)
library(questionr)
library(pROC)
# Realizamos carga de los sets de datos
datosTrain<-readRDS("datosTrain")
datosValidacion<-readRDS("datosValidacion")
#=========================================================================
# Modelo 1 30 variables importantes de Random Forest
#=========================================================================
# Definimos la grilla de control
control<-trainControl(method = "cv",
number=4,
savePredictions = "all",
classProbs=TRUE)
# Modelo
set.seed(1234) # Misma semilla que Random Forest
multinom1 <- train(varObj~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+assigned_room_type.A+
arrival_date_week_number+required_car_parking_spaces+
customer_type.Transient_Party+arrival_date_year.2015+
stays_in_week_nights+booking_changes+arrival_date_year.2017+
arrival_date_year.2016+market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data = datosTrain,
method = "multinom",
trControl = control
)
# Revisamos resultados
multinom1
a<-subset(multinom1,multinom1$pred$decay==0)
a<-as.data.frame(a[5]$pred)
# Calculamos matriz de confusión para mejor modelo, decay =0
confusionMatrix(a$pred,a$obs)
# Realizamos predicciones sobre el set de validación
p1<-predict(multinom1,datosValidacion[,-40])
# Vemos la matriz de confusión
confusionMatrix(p1,datosValidacion$varObj)
#-------------------------------------------------------------------------
# Validación cruzada repetida
#-------------------------------------------------------------------------
set.seed(4321) # Cambiamos semilla para ver muestra distinta
control<-trainControl(method = "repeatedcv",
number=10,
repeats=10,
savePredictions = "all",
classProbs=TRUE)
# Corremos modelo
multinom2 <- train(varObj~fe_country+lead_time+total_of_special_requests+
fe_agent+fe_longLat+deposit_type.Deposit+deposit_type.No_Deposit+
fe_totalFactura+market_segment.Online_TravelAgents+Av_DailyRate+
previous_cancellations+customer_type.Transient+assigned_room_type.A+
arrival_date_week_number+required_car_parking_spaces+
customer_type.Transient_Party+arrival_date_year.2015+
stays_in_week_nights+booking_changes+arrival_date_year.2017+
arrival_date_year.2016+market_segment.Groups+
distribution_channel.TravAgents_TurismOperator+hotel.Resort_Hotel+
market_segment.Other+hotel.City_Hotel+stays_in_weekend_nights+
fe_diaSemanaLlegada+market_segment.Offline_TravAgentsTurOperators+
reserved_room_type.A,
data = datosTrain,
method = "multinom",
trControl = control
)
# Rescatamos las predicciones como data frame
multinom2$pred
preditest<-multinom2$pred
# Obtenemos las predicciones del mejor modelo entrenado
a<-subset(preditest,preditest$decay==1e-04)
# Matriz de confusión del mejor modelo
confusionMatrix(a$pred,a$obs)
# Construimos tabla para accuracy por repeticion
preditest$prueba<-strsplit(preditest$Resample,"[.]")
preditest$Fold <- sapply(preditest$prueba, "[", 1)
preditest$Rep <- sapply(preditest$prueba, "[", 2)
preditest$prueba<-NULL
# Función de cálculo de accuracy
tasaAciertos<-function(x,y) {
confu<-confusionMatrix(x,y)
tasa<-confu[[3]][1]
return(tasa)
}
# Aplicamos función de accuracy por cada repetición
tabla<-table(preditest$Rep)
listarep<-c(names(tabla))
medias<-data.frame()
for (repi in listarep) {
paso1<-preditest[which(preditest$Rep==repi),]
acc=tasaAciertos(paso1$pred,paso1$obs)
medias<-rbind(medias,acc)
}
names(medias)<-"accuracy"
# Revisamos los resultados
medias
# Guardamos los datos de los accuracy
saveRDS(medias,"modMultinomial")
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 24.042 | 4.022 | 225 | 28.289 |
| Check_Out | 6.070 | 48.595 | 620 | 55.285 |
| No_Show | 0 | 0 | 0 | 0 |
| Total | 30.112 | 52.617 | 845 | 83.574 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 72.637 | |||
| Accuracy | 86,9% |
| Canceled | Check_Out | No_Show | Total | |
|---|---|---|---|---|
| Canceled | 6.654 | 1.950 | 0 | 8.604 |
| Check_Out | 949 | 14.085 | 0 | 15.034 |
| No_Show | 63 | 179 | 0 | 242 |
| Total | 7.666 | 16.214 | 0 | 23.880 |
| ———– | ———– | ———– | ———– | ———– |
| Diagonal | 20.739 | |||
| Accuracy | 86,8% |
#-----------------------------------------------------------------
# VCR Modelos Caret
#-----------------------------------------------------------------
# Realizamos carga de los accuracy de las cv para los 4 modelos
rf<-readRDS("modRandomForest")
multinom<-readRDS("modMultinomial")
xgboost<-readRDS("modXgboost")
nnet<-readRDS("modRnnet")
# Insertamos columna con nombre del modelo
rf$modelo<-"RndForest"
multinom$modelo<-"Multinomial"
xgboost$modelo<-"Xgboost"
nnet$modelo<-"Nnet"
# Unimos los accuracy en solo 1 data frame
union<-rbind(rf,multinom,xgboost,nnet)
# Reordenamos el set de datos con los modelos por su mediana
union$modelo <- with(union,reorder(modelo,accuracy, median))
# Graficamos
par(cex.axis=0.8,las=2)
boxplot(data=union,accuracy~modelo,
main="Comparación Accuracy modelos \n librería Caret - Cross Validation",
col="orange1")
#-----------------------------------------------------------------
# VCR Modelos H2O
#-----------------------------------------------------------------
# Realizamos carga de datos de las VCR de H2O
modH2o<-read.csv2("modelos_h2o.txt",sep="\t")
# Reordenamos el set de datos con los modelos por su mediana
modH2o$modelo <- with(modH2o,reorder(modelo,accuracy, median))
# Graficamos
par(cex.axis=0.8,las=1)
boxplot(data=modH2o,accuracy~modelo,
main="Comparación Accuracy modelos \n librería H2O - Cross Validation",
col="orange1")
#-----------------------------------------------------------------
# Mejor modelo Caret vs mejor modelo H2O
#-----------------------------------------------------------------
a<-subset(union,union$modelo=="Xgboost") # Filtramos para los datos de Xgboost
a$modelo<-"Xgboost_Caret" # Renombramos la columna
b<-subset(modH2o,modH2o$modelo=="Stack_GLM_H2O")# Filtramos para los datos de Stack GLM
b$modelo<-"Stack_GLM_H20" # Renombramos la columna
c<-rbind(a,b) # Unimos los frames
# Reordenamos el set de datos con los modelos por su mediana
c$modelo <- with(c,reorder(modelo,accuracy, median))
# Graficamos
par(cex.axis=0.8,las=1)
boxplot(data=c,accuracy~modelo,
main="Comparación mejores modelos Caret y H2O",
col="orange1")
| cv - Rep | RndForest | Multinomial | Xgboost | Nnet |
|---|---|---|---|---|
| 1 | 0,8585 | 0,8124 | 0,8846 | 0,8640 |
| 2 | 0,8584 | 0,8125 | 0,8860 | 0,8637 |
| 3 | 0,8581 | 0,8123 | 0,8842 | 0,8639 |
| 4 | 0,8581 | 0,8122 | 0,8838 | 0,8645 |
| 5 | 0,8582 | 0,8124 | 0,8850 | 0,8634 |
| 6 | 0,8579 | 0,8123 | 0,8850 | 0,8633 |
| 7 | 0,8586 | 0,8122 | 0,8841 | 0,8626 |
| 8 | 0,8584 | 0,8124 | 0,8845 | 0,8639 |
| 9 | 0,8585 | 0,8123 | 0,8850 | 0,8624 |
| 10 | 0,8584 | 0,8123 | 0,8838 | 0,8639 |
| ———- | ———- | ———- | ———- | ———- |
| Media | 0,8583 | 0,8123 | 0,8846 | 0,8636 |
| Mediana | 0,8584 | 0,8123 | 0,8845 | 0,8638 |
| StdD | 0,0002 | 0,0001 | 0,0007 | 0,0006 |
Cuadro muestra un resumen de los resultados obtenidos para los 4 modelos Caret en el proceso de validación cruzada repetida (VCR) para 10 grupos. La primera columna muestra el grupo de VCR a que fue sometido el algoritmo, los valores en columnas muestran el accuracy obtenido sobre el set de datos de entrenamiento luego del proceso. Mejor modelo es Xgboost con 88,45% de accuracy en su mediana.
# Carga de librerias a usar
library(h2o)
library(caret)
library(pROC)
library(dbplyr)
library(magrittr)
library(tidyverse)
library(ggpubr)
h2o.init()
#-----------------------------------------------------------------------
# Preparación datos entrenamiento
#-----------------------------------------------------------------------
# Cargamos datos
datosTrain<-readRDS("datosTrain")
# Preparamos el set de datos para trabajar con H2O.
# Usamos las mejores variables definidas por Random Forest
x<-c(datosTrain[60],datosTrain[41],datosTrain[54],datosTrain[59],datosTrain[55],
datosTrain[35],datosTrain[36],datosTrain[58],datosTrain[17],datosTrain[45],
datosTrain[49],datosTrain[38],datosTrain[28],datosTrain[42],datosTrain[53],
datosTrain[39],datosTrain[44],datosTrain[3],datosTrain[51],datosTrain[5],
datosTrain[4],datosTrain[15],datosTrain[21],datosTrain[2],datosTrain[18],
datosTrain[1],datosTrain[43],datosTrain[57],datosTrain[16],datosTrain[24])
# Transformamos a data frame de H2O
x<-as.data.frame(x)
y<-c(datosTrain[40])
train<-cbind(y,x)
train<-as.h2o(train)
#-----------------------------------------------------------------------
# Preparación datos validación
#-----------------------------------------------------------------------
# Cargamos datos
datosValidacion<-readRDS("datosValidacion")
# Preparamos el set de datos para trabajar con H2O.
# Usamos las mejores variables definidas por Random Forest
xVal<-c(datosValidacion[60],datosValidacion[41],datosValidacion[54],datosValidacion[59],
datosValidacion[55],datosValidacion[35],datosValidacion[36],datosValidacion[58],
datosValidacion[17],datosValidacion[45],datosValidacion[49],datosValidacion[38],
datosValidacion[28],datosValidacion[42],datosValidacion[53],datosValidacion[39],
datosValidacion[44],datosValidacion[3],datosValidacion[51],datosValidacion[5],
datosValidacion[4],datosValidacion[15],datosValidacion[21],datosValidacion[2],
datosValidacion[18],datosValidacion[1],datosValidacion[43],datosValidacion[57],
datosValidacion[16],datosValidacion[24])
# Transformamos a data frame de H2O
xVal<-as.data.frame(xVal)
yVal<-c(datosValidacion[40])
validacion<-cbind(yVal,xVal)
validacion<-as.h2o(validacion)
#-----------------------------------------------------------------------
# Preparación datos test
#-----------------------------------------------------------------------
# Cargamos datos
datosTest<-readRDS("datosTest")
# Preparamos el set de datos para trabajar con H2O.
# Usamos las mejores variables definidas por Random Forest
xTest<-c(datosTest[60],datosTest[41],datosTest[54],datosTest[59],datosTest[55],
datosTest[35],datosTest[36],datosTest[58],datosTest[17],datosTest[45],
datosTest[49],datosTest[38],datosTest[28],datosTest[42],datosTest[53],
datosTest[39],datosTest[44],datosTest[3],datosTest[51],datosTest[5],
datosTest[4],datosTest[15],datosTest[21],datosTest[2],datosTest[18],
datosTest[1],datosTest[43],datosTest[57],datosTest[16],datosTest[24])
# Transformamos a data frame de H2O
xTest<-as.data.frame(xTest)
yTest<-c(datosTest[40])
test<-cbind(yTest,xTest)
test<-as.h2o(test)
Interfaz web H2O
Los modelos H2O fueron construidos sin código, y directamente desde la aplicación web de H2O: Flow. Previa modelización, es requisito inicializar el cluster en R por con h2o.init(). Por medio de esta acción, se habilita el acceso al puerto local #54321 en el browser.
En R se realizó el tratamiento previo de datos, es decir, su transformación de data frames de R a data frames de H2O. La interfaz, automáticamente detecta los datos transformados, con lo que se puede modelar directamente.
| label | type | level | actual_value | default_value |
|---|---|---|---|---|
| model_id | Key | critical | Modelo GbmH2o_cv | |
| nfolds | int | critical | 10 | 0 |
| keep_cv_models | boolean | expert | true | true |
| keep_cv_predictions | boolean | expert | true | false |
| keep_cv_fold_assignment | boolean | expert | false | false |
| score_each_iteration | boolean | secondary | false | false |
| score_tree_interval | int | secondary | 0 | 0 |
| fold_assignment | enum | secondary | AUTO | AUTO |
| response_column | VecSpecifier | critical | varObj | |
| ignore_const_cols | boolean | critical | true | true |
| balance_classes | boolean | secondary | false | false |
| max_after_balance_size | float | expert | 5 | 5 |
| max_confusion_matrix_size | int | secondary | 20 | 20 |
| max_hit_ratio_k | int | secondary | 0 | 0 |
| ntrees | int | critical | 375 | 50 |
| max_depth | int | critical | 60 | 5 |
| min_rows | double | critical | 10 | 10 |
| nbins | int | critical | 20 | 20 |
| nbins_top_level | int | secondary | 1024 | 1024 |
| nbins_cats | int | secondary | 1024 | 1024 |
| r2_stopping | double | secondary | 1,79e+308 | 1,79e+308 |
| stopping_rounds | int | secondary | 0 | 0 |
| stopping_metric | enum | secondary | AUTO | AUTO |
| stopping_tolerance | double | secondary | 0.001 | 0.001 |
| max_runtime_secs | double | secondary | 0 | 0 |
| seed | long | critical | 4321 | -1 |
| build_tree_one_node | boolean | expert | false | false |
| learn_rate | double | critical | 0.01 | 0.1 |
| learn_rate_annealing | double | secondary | 1 | 1 |
| distribution | enum | secondary | multinomial | AUTO |
| quantile_alpha | double | secondary | 0.5 | 0.5 |
| tweedie_power | double | secondary | 1.5 | 1.5 |
| huber_alpha | double | secondary | 0.9 | 0.9 |
| sample_rate | double | critical | 1 | 1 |
| col_sample_rate | double | critical | 1 | 1 |
| col_sample_rate_change_per_level | double | expert | 1 | 1 |
| col_sample_rate_per_tree | double | secondary | 1 | 1 |
| min_split_improvement | double | secondary | 0.00001 | 0.00001 |
| histogram_type | enum | secondary | AUTO | AUTO |
| max_abs_leafnode_pred | double | expert | 1.79e+308 | 1.79e+308 |
| pred_noise_bandwidth | double | expert | 0 | 0 |
| categorical_encoding | enum | secondary | AUTO | AUTO |
| calibrate_model | boolean | expert | false | false |
| check_constant_response | boolean | expert | true | true |
Cuadro muestra los parámetros usados y los por defecto, al correr modelo Gradient Boosting en librería H2O.
| label | type | level | actual_value | default_value |
|---|---|---|---|---|
| model_id | Key | critical | deepLearning2_cv | |
| nfolds | int | critical | 10 | 0 |
| keep_cv_models | boolean | expert | false | true |
| keep_cv_predictions | boolean | expert | true | false |
| keep_cv_fold_assignment | boolean | expert | false | false |
| fold_assignment | enum | secondary | AUTO | AUTO |
| response_column | VecSpecifier | critical | varObj | |
| ignore_const_cols | boolean | critical | false | true |
| score_each_iteration | boolean | secondary | false | false |
| balance_classes | boolean | secondary | false | false |
| max_after_balance_size | float | expert | 5 | 5 |
| max_confusion_matrix_size | int | secondary | 3 | 20 |
| max_hit_ratio_k | int | secondary | 0 | 0 |
| overwrite_with_best_model | boolean | expert | false | true |
| use_all_factor_levels | boolean | secondary | false | true |
| standardize | boolean | secondary | false | true |
| activation | enum | critical | Tanh | Rectifier |
| hidden | int[] | critical | 512 | 200200 |
| epochs | double | critical | 13,62 | 10 |
| train_samples_per_iteration | long | secondary | -2 | -2 |
| target_ratio_comm_to_comp | double | expert | -2 | 0.05 |
| seed | long | expert | 4321 | -1 |
| adaptive_rate | boolean | secondary | false | true |
| rho | double | expert | 0.99 | 0.99 |
| epsilon | double | expert | 1,00E-08 | 1,00E-08 |
| rate | double | expert | 0.0001 | 0.005 |
| rate_annealing | double | expert | 0.000001 | 0.000001 |
| rate_decay | double | expert | 0 | 1 |
| momentum_start | double | expert | 0.5 | 0 |
| momentum_ramp | double | expert | 1000000 | 1000000 |
| momentum_stable | double | expert | 0.99 | 0 |
| nesterov_accelerated_gradient | boolean | expert | true | true |
| input_dropout_ratio | double | secondary | 0 | 0 |
| l1 | double | secondary | 0 | 0 |
| l2 | double | secondary | 0 | 0 |
| max_w2 | float | expert | 3,40E+45 | 3,40E+45 |
| initial_weight_distribution | enum | expert | UniformAdaptive | UniformAdaptive |
| initial_weight_scale | double | expert | 1 | 1 |
| loss | enum | secondary | CrossEntropy | Automatic |
| distribution | enum | secondary | multinomial | AUTO |
| quantile_alpha | double | secondary | 0.5 | 0.5 |
| tweedie_power | double | secondary | 1.5 | 1.5 |
| huber_alpha | double | secondary | 0.9 | 0.9 |
| score_interval | double | secondary | 5 | 5 |
| score_training_samples | long | secondary | 10000 | 10000 |
| score_validation_samples | long | secondary | 2500 | 0 |
| score_duty_cycle | double | secondary | 0.1 | 0.1 |
| classification_stop | double | expert | 0 | 0 |
| regression_stop | double | expert | 0.000001 | 0.000001 |
| stopping_rounds | int | secondary | 0 | 5 |
| stopping_metric | enum | secondary | logloss | AUTO |
| stopping_tolerance | double | secondary | 0.001 | 0 |
| max_runtime_secs | double | secondary | 0 | 0 |
| score_validation_sampling | enum | expert | Stratified | Uniform |
| diagnostics | boolean | expert | true | true |
| fast_mode | boolean | expert | true | true |
| force_load_balance | boolean | expert | true | true |
| variable_importances | boolean | critical | false | true |
| replicate_training_data | boolean | secondary | true | true |
| single_node_mode | boolean | expert | false | false |
| shuffle_training_data | boolean | expert | false | false |
| missing_values_handling | enum | expert | MeanImputation | MeanImputation |
| quiet_mode | boolean | expert | false | false |
| autoencoder | boolean | secondary | false | false |
| sparse | boolean | expert | false | false |
| col_major | boolean | expert | false | false |
| average_activation | double | expert | 0 | 0 |
| sparsity_beta | double | expert | 0 | 0 |
| max_categorical_features | int | expert | 2147483647 | 2147483647 |
| reproducible | boolean | expert | false | false |
| export_weights_and_biases | boolean | expert | false | false |
| mini_batch_size | int | expert | 1 | 1 |
| categorical_encoding | enum | secondary | AUTO | AUTO |
| elastic_averaging | boolean | expert | false | false |
| elastic_averaging_moving_rate | double | expert | 0.9 | 0.9 |
| elastic_averaging_regularization | double | expert | 0.001 | 0.001 |
Cuadro muestra los parámetros usados y los por defecto, al correr modelo Deep Learning 2, en librería H2O.
Configuración de Deep Learning 2 H2O
Cuadro muestra la estructura de la red del modelo Deep Learning 2, con solo 1 capa oculta con 512 neuronas.
| label | type | level | actual_value | default_value |
|---|---|---|---|---|
| model_id | Key | critical | deepLearning4_cv | |
| nfolds | int | critical | 10 | 0 |
| keep_cv_models | boolean | expert | false | true |
| keep_cv_predictions | boolean | expert | true | false |
| keep_cv_fold_assignment | boolean | expert | false | false |
| fold_assignment | enum | secondary | AUTO | AUTO |
| response_column | VecSpecifier | critical | varObj | |
| ignore_const_cols | boolean | critical | false | true |
| score_each_iteration | boolean | secondary | false | false |
| balance_classes | boolean | secondary | false | false |
| max_after_balance_size | float | expert | 5 | 5 |
| max_confusion_matrix_size | int | secondary | 3 | 20 |
| max_hit_ratio_k | int | secondary | 0 | 0 |
| overwrite_with_best_model | boolean | expert | false | true |
| use_all_factor_levels | boolean | secondary | false | true |
| standardize | boolean | secondary | false | true |
| activation | enum | critical | Tanh | Rectifier |
| hidden | int[] | critical | 32,32,32,32,32 | 200,200 |
| epochs | double | critical | 20,5 | 10 |
| train_samples_per_iteration | long | secondary | -2 | -2 |
| target_ratio_comm_to_comp | double | expert | -2 | 0.05 |
| seed | long | expert | 4321 | -1 |
| adaptive_rate | boolean | secondary | false | true |
| rho | double | expert | 0.99 | 0.99 |
| epsilon | double | expert | 1,00E-08 | 1,00E-08 |
| rate | double | expert | 0.0001 | 0.005 |
| rate_annealing | double | expert | 0.000001 | 0.000001 |
| rate_decay | double | expert | 0 | 1 |
| momentum_start | double | expert | 0.5 | 0 |
| momentum_ramp | double | expert | 1000000 | 1000000 |
| momentum_stable | double | expert | 0.99 | 0 |
| nesterov_accelerated_gradient | boolean | expert | true | true |
| input_dropout_ratio | double | secondary | 0 | 0 |
| l1 | double | secondary | 0 | 0 |
| l2 | double | secondary | 0 | 0 |
| max_w2 | float | expert | 3,40E+45 | 3,40E+45 |
| initial_weight_distribution | enum | expert | UniformAdaptive | UniformAdaptive |
| initial_weight_scale | double | expert | 1 | 1 |
| loss | enum | secondary | CrossEntropy | Automatic |
| distribution | enum | secondary | multinomial | AUTO |
| quantile_alpha | double | secondary | 0.5 | 0.5 |
| tweedie_power | double | secondary | 1.5 | 1.5 |
| huber_alpha | double | secondary | 0.9 | 0.9 |
| score_interval | double | secondary | 5 | 5 |
| score_training_samples | long | secondary | 10000 | 10000 |
| score_validation_samples | long | secondary | 2500 | 0 |
| score_duty_cycle | double | secondary | 0.1 | 0.1 |
| classification_stop | double | expert | 0 | 0 |
| regression_stop | double | expert | 0.000001 | 0.000001 |
| stopping_rounds | int | secondary | 0 | 5 |
| stopping_metric | enum | secondary | logloss | AUTO |
| stopping_tolerance | double | secondary | 0.001 | 0 |
| max_runtime_secs | double | secondary | 0 | 0 |
| score_validation_sampling | enum | expert | Stratified | Uniform |
| diagnostics | boolean | expert | true | true |
| fast_mode | boolean | expert | true | true |
| force_load_balance | boolean | expert | true | true |
| variable_importances | boolean | critical | false | true |
| replicate_training_data | boolean | secondary | true | true |
| single_node_mode | boolean | expert | false | false |
| shuffle_training_data | boolean | expert | false | false |
| missing_values_handling | enum | expert | MeanImputation | MeanImputation |
| quiet_mode | boolean | expert | false | false |
| autoencoder | boolean | secondary | false | false |
| sparse | boolean | expert | false | false |
| col_major | boolean | expert | false | false |
| average_activation | double | expert | 0 | 0 |
| sparsity_beta | double | expert | 0 | 0 |
| max_categorical_features | int | expert | 2147483647 | 2147483647 |
| reproducible | boolean | expert | false | false |
| export_weights_and_biases | boolean | expert | false | false |
| mini_batch_size | int | expert | 1 | 1 |
| categorical_encoding | enum | secondary | AUTO | AUTO |
| elastic_averaging | boolean | expert | false | false |
| elastic_averaging_moving_rate | double | expert | 0.9 | 0.9 |
| elastic_averaging_regularization | double | expert | 0.001 | 0.001 |
Cuadro muestra los parámetros usados y los por defecto, al correr modelo Deep Learning 4 en librería H2O.
Configuración de Deep Learning 4 H2O
Cuadro muestra la estructura de la red del modelo Deep Learning 4, con 5 capas ocultas con 32 neuronas cada una.
Funciones de error Train/Validación GBM H2O - Azul:Train, Naranja:Validación
La gráfica de los errores en Gradient Boosting muestra que ellos se reducen en la medida que avanza el número de iteraciones. la baja es continua. La gráfica naranja, que muestra el error en el set e datos de validación no muestra incrementos, por lo que es indicativo de la no existencia de sobreajuste. Modelo es detenido su entrenamiento a las 375 iteraciones.
Funciones de error Train/Validación Deep Learning 2
Gráfica de los errores para el modelo de Deep Learning 2 -1 capa oculta con 512 neuronas-, muestra alta variación a medida que avanzan las iteraciones. Las gráficas de entrenamiento y validación se mueven juntas.
Funciones de error Train/Validación Deep Learning 4
Gráfica de los errores para el modelo de Deep Learning 4 -5 capas ocultas con 32 neuronas en cada una-, muestra un patrón de errores decreciente a medida que avanzan las iteraciones. Curva de entrenamiento y validación, se mueven juntas. Al comparar versus la gráfica de Deep Learing 2, vemos que es preferible más capas ocultas con menos neuronas, que menos capas ocultas con un mayor número de neuronas.
| cv - Rep | GBM | Deep Learn 2 | Deep Learn 4 | Stack GLM | Stack RF | Stack GBM |
|---|---|---|---|---|---|---|
| cv_1_valid | 0,8816 | 0,7718 | 0,7884 | 0,8945 | 0,8932 | 0,8975 |
| cv_2_valid | 0,8870 | 0,8381 | 0,8785 | 0,8869 | 0,8856 | 0,8874 |
| cv_3_valid | 0,8871 | 0,8571 | 0,8529 | 0,8927 | 0,8856 | 0,8902 |
| cv_4_valid | 0,8856 | 0,8182 | 0,8471 | 0,8947 | 0,8796 | 0,8919 |
| cv_5_valid | 0,8854 | 0,8000 | 0,8154 | 0,8895 | 0,8874 | 0,8878 |
| cv_6_valid | 0,8856 | 0,8238 | 0,8320 | 0,8961 | 0,9029 | 0,8970 |
| cv_7_valid | 0,8890 | 0,8411 | 0,8566 | 0,8972 | 0,8968 | 0,8943 |
| cv_8_valid | 0,8938 | 0,8857 | 0,8898 | 0,8898 | 0,8866 | 0,8902 |
| cv_9_valid | 0,8912 | 0,8247 | 0,8526 | 0,8967 | 0,8902 | 0,8945 |
| cv_10_valid | 0,8879 | 0,8178 | 0,8340 | 0,8963 | 0,8971 | 0,8930 |
| ————- | ————- | ————- | ————- | ————- | ————- | ————- |
| mean | 0,8874 | 0,8278 | 0,8447 | 0,8934 | 0,8905 | 0,8924 |
| median | 0,8871 | 0,8247 | 0,8471 | 0,8945 | 0,8902 | 0,8924 |
| sd | 0,0034 | 0,0310 | 0,0294 | 0,0036 | 0,0069 | 0,0035 |
Cuadro muestra un resumen de los resultados obtenidos para los 6 modelos de la librería H2O en el proceso de validación cruzada repetida (VCR) para 10 grupos. La primera columna muestra el grupo de VCR a que fue sometido el algoritmo, los valores en columnas muestran el accuracy obtenido sobre el set de datos de entrenamiento luego del proceso. Mejor modelo es el ensamblado con GLM, con un 89,45% de accuracy en su mediana.
Gráficas de error para los modelos ensamblados.
Gráfica de las funciones de error para los modelos ensamblados. Es posible ver la bondad de estos modelos dado que incorporan la estabilidad del modelo Gradient Boosting, suavizando las curvas de errores a lo largo de las iteraciones.
Media pensión.↩
Pensión completa.↩
Por confidencialidad de la información, no se entrega detalle de qué tipo de habitación se trata cada letra.↩
Por confidencialidad de la información, no se entrega detalle de qué tipo de habitación se trata cada letra.↩
Cliente individual en estadía corta.↩
Cliente en estadía corta acompañado.↩
Cliente con tarifa corporativa.↩
Reserva masiva.↩
El diagrama que muestra la distribución por países de los viajeros que realizan reservas de hotel en la base puede consultarse en el anexo 1.↩
Revisar anexo 2 con gráficas del perfil básico del viajero de la base de datos.↩
No se queda los fines de semana, dado que vuelve a casa. Esta es otra razón para considerar al viajero promedio como un viajero de negocios.↩
El pago refundable lo interpretamos como aquel cargo que se realiza con la tarjeta de crédito, y que luego es reversado al momento del check-out.↩
Revisar anexos 3 y 4 con estadísticas del viajero, desde el punto de vista del negocio hotelero.↩
Ver Anexo 5 con cuadro detalle.↩
Ver anexo 6.↩
Ver anexo 7 con gráfica de nuestra variable objetivo.↩
Si es así, por qué motivo ?.↩
Ver anexo 8 con este detalle.↩
Ver anexo 9.↩
Ver anexo 10.↩
Ver anexo 11.↩
Ver anexo 12 con las gráficas para las cancelaciones por mes y día.↩
Recordemos que no se tiene información del origen de la base de datos.↩
Se descartó el uso de la métrica AUC (área bajo la curva ROC) por cuánto nuestro set de datos es multiclase, y el AUC solo está definido para sets binarios. No obstante existe la función multiclassRoc() en R, verificamos que ésta no genera resultados consistentes entre los distintos sets.↩
Revisar anexo 13 con código R para la depuración de datos.↩
Revisar anexo 14 con código R para feature engineering.↩
Revisar anexo 15 con gráfica de correlaciones entre las variables numéricas.↩
En los anexos 16 y 17 se pueden observar las gráficas de las variables antes y después de sus transformaciones.↩
El detalle completo de las variables utilizadas, puede verse en el anexo 18.↩
Ver anexo 19 con código R para los modelos Random Forest.↩
Recordar que los porcentajes fueron Check-out 63%, Cancelación 36% y No presentación 1%.↩
Revisar anexos 20 y 21 con desempeño de accuracy en modelos 1 y 2.↩
Ver las matrices de confusión de RStudio para el modelo 3 en los anexos 22 y 23.↩
Revisar punto de selección de variables.↩
Al reducir el número de variables debe cambiar el mtry. En este caso consideramos el sqrt(# variables).↩
Ver anexo 24 con código R para los modelos Multinomial.↩
Ver anexo 25 con código R para los modelos Xgboost.↩
Revisar anexo 26 con gráfica del modelo 1 para el tunning de hiperparámetros.↩
La gráfica de este entrenamiento puede verse en el anexo 27.↩
Parámetros eta = 0.05, min_child_weight=5 y nrounds=3000.↩
Ver anexo 28 con código R para los modelos de redes neuronales de Caret.↩
Número de nodos -neuronas- en capa oculta,↩
Tasa de aprendizaje - learning rate.↩
30 nodos en capa input, 20 nodos en capa intermedia y 3 nodos en capa output. Para todas las capas, la función de activación fue softmax.↩
Las matrices de confusión pueden verse en los anexos 29 y 30.↩
Samsung Galaxy Book 12, i5-7200, 8Gb RAM y w10x64.↩
Máquina virtual n1-standard-16: 16 CPU y 60Gb RAM.↩
Código de R para proceso de validación cruzada de los modelos Caret, puede verse en el anexo 31.↩
Si el algoritmo lo requería.↩
La tabla de base para boxplot() puede verse en el anexo 32.↩
Recordar que sobre el set de validación obtuvo un accuracy de un 88,9%.↩
El código de preparación de los datos para ser usados en H2O, puede consultarse en el anexo 33.↩
Un print de la interfaz Flow, puede verse en el anexo 34.↩
Entre otras ventajas de H2O, podemos mencionar que permite el entrenamiento de un algoritmo en tiempo real, a diferencia de Caret, donde los algoritmos se visualizan una vez entrenados. Esto permite detener / mejorar un modelo, si el procesamiento de datos toma mucho tiempo.↩
Recordar que en Caret no fue posible su desarrollo.↩
Detalle de los parámetros seleccionados para correr el modelo GBM pueden verse en el anexo 35.↩
Revisar gráfica de errores en anexo 40.↩
En cada configuración, el primer valor corresponde al número de neuronas en la capa input, el último, a al número de neuronas en la capa output. Los valores intermedios, corresponden al número de neuronas en las capas ocultas.↩
Ver anexos 36 a 39 con detalle de los parámetros usados para correr ambas redes.↩
Revisar anexo 41 con gráfica de errores para el modelo Deep Learning 2 de H2O.↩
Ver anexo 42 con gráfica de errores para modelo Deep Learing 4 con H2O.↩
Ver anexo 44 con gráficas de funciones de error para cada ensamblado.↩
Ver anexo 43 con datos de origen para la gráfica del proceso de VCR en H2O.↩
App 88,5% en Xgboots vs 89,5% en stacked GLM.↩